Используют ли какие-либо хэш-таблицы (в памяти, нераспределенные) согласованное хеширование?

Я не говорю о распределенных системах ключ/значение, таких как обычно используемые с memcached, которые используют согласованное хеширование, чтобы сделать добавление/удаление узлов относительно дешевой процедурой.

Я говорю о вашей стандартной хеш-таблице в памяти, такой как python dict или хэш perl.

Казалось бы, преимущества использования согласованного хеширования также применимы к этим стандартным структурам данных за счет снижения стоимости изменения размера хеш-таблицы. Системы реального времени (и другие системы, чувствительные к задержкам) выиграют от / потребуют хеш-таблиц, оптимизированных для малозатратного роста, даже если общая пропускная способность немного снизится.

Википедия ссылается на «постепенное изменение размера», но в основном говорит о подходе к изменению размера с горячей/холодной заменой; есть отдельная статья о «расширяемом хешировании», в которой для дешевого повторного хэширования используется поиск в сегменте.

Просто любопытно, слышал ли кто-нибудь о встроенных в ядро ​​хеш-таблицах с одним узлом, которые используют последовательное хеширование для снижения стоимости роста. Или это требование лучше выполнить, используя какой-то другой подход (аля два бита из Википедии, перечисленные выше)?

или ... весь мой вопрос ошибочен? Делают ли соображения подкачки памяти сложность того не стоит? То есть дополнительная косвенность последовательного хеширования позволяет повторно хешировать только часть всех ключей, но, возможно, это не имеет значения, потому что вам, вероятно, придется читать с каждой существующей страницы, поэтому задержка памяти является вашим основным фактором, и вы перефразируете некоторые или все ключи, не имеет значения по сравнению со стоимостью доступа к памяти.... но, с другой стороны, при последовательном хешировании все ваши переназначения ключей имеют одну и ту же страницу назначения, поэтому будет меньшая перегрузка памяти, чем если бы ваши ключи переназначались на любую из существующих страниц.

РЕДАКТИРОВАТЬ: добавлен тег «структуры данных», уточнено последнее предложение, чтобы сказать «страница» вместо «ведро».


person John Hart    schedule 04.01.2013    source источник
comment
Я, конечно, не вижу смысла в беглом взгляде на описание Википедии. Кажется, что вы только сохраняете повторное хеширование и некоторую перетасовку таблиц, но хэш-функция в любом случае должна быть быстрой, перемещение записей дешево (в отличие от распределенного контекста), а изменение размера происходит редко ( с приличной политикой роста), а дополнительная косвенность замедлила бы все поиски. Но, возможно, я что-то упускаю.   -  person    schedule 05.01.2013
comment
delnan - да, вы экономите только на повторном хешировании за счет другого доступа к памяти при каждом поиске. Но если вы чувствительны к задержкам, вы не обязательно можете позволить себе большой незапланированный повтор. Подобно тому, почему люди не пишут системы реального времени на языках со сборкой мусора.   -  person John Hart    schedule 05.01.2013


Ответы (1)


Я не слышал об этом в дикой природе, но это может быть хорошей идеей, если вы выберете правильную непротиворечивую реализацию хеширования. В частности, Jump Consistent Hashing от Google et al. Сначала я расскажу, почему Jump, а затем расскажу, как он может быть полезен в локальной структуре данных.

Согласованное хеширование с переходом

Согласованное хеширование с переходом (которое я буду сокращать до Jump) отлично подходит для этой области по нескольким причинам. Jump предполагает, что узлы не выходят из строя, что отлично подходит для локальных структур данных, потому что они не выходят из строя! Это позволяет Jump просто отображать диапазон чисел [0, numBuckets), требуя всего 2-4 байта пространства.

Далее реализация проста и быстра. И это даже быстрее, если мы удалим деление с плавающей запятой эталонной реализации и заменим его вдвое меньшим количеством целочисленных делений. (Что мы можем, кстати.)

Все это можно использовать для вариации на...

ConcurrentHashMap

Но сначала Конкурентная хеш-карта на высоком уровне.

ConcurrentHashMap в Java параметризуется несколькими сегментами. Этот коэффициент шардинга остается постоянным на протяжении всей жизни карты. Каждая из этих корзин сама по себе является хэш-картой со своей блокировкой.

При вставке пары ключ-значение в карту ключ хэшируется в одну из корзин. Блокировка для этого ключа берется, и элемент вставляется в хэш-карту корзины перед снятием блокировки. Во время вставки в корзину x другой поток может одновременно выполнять вставку в корзину y, но он будет ждать блокировки при вставке в корзину x. Таким образом, Java ConcurrentHashMap имеет n-сторонний параллелизм, где n — это параметр bucket конструктора.

Как и любая хеш-карта, сегмент в ConcurrentHashMap может заполниться, и ему нужно будет расти. Как и обычная хеш-карта, он делает это, удваивая свой размер и перефразируя все в ведре обратно в более крупное «я». За исключением того, что «его большее я» — это только «я» ведра. Если ведро является горячей точкой и получает больше ключей, чем ему положено, оно будет расти непропорционально по сравнению с другими ведрами. И каждый раз, когда ведро растет, требуется все больше и больше времени, чтобы перефразировать его. Этот последний пункт является проблемой не только для горячих точек, но и когда старая хеш-таблица получает больше ключей.

Представьте, если бы мы могли увеличивать количество сегментов по мере роста количества ключей. Таким образом, мы могли бы ослабить рост каждого отдельного ведра.

Введите согласованное хеширование, что позволит нам добавить больше сегментов!

ConcurrentHashMap дубль 2: согласованный стиль хеширования

Мы можем заставить ConcurrentHashMap увеличить количество корзин за два простых шага.

Сначала замените функцию, которая сопоставляется с каждым сегментом, хэш-функцией с согласованным переходом. Пока все должно работать одинаково.

Второй отделить новое ведро, когда ведро заполнено; также вырастить наполненное ведро. На самом деле, отделяйте новое ведро только в том случае, если заполненное ведро становится самым большим с точки зрения вместимости. Это можно рассчитать без повторения ведер.

При последовательном хешировании разделение будет направлять ключи только в новое ведро, а не обратно в какое-либо из старых ведер.

Конечные примечания

Я уверен, что эту схему можно улучшить. А именно, отделение корзины требует полного сканирования таблицы, чтобы переместить ключи в новую корзину. Это, безусловно, не хуже, чем ванильная хеш-карта, и, вероятно, лучше, но это невыгодно реализации ConcurrentHashMap, которая, вероятно, не должна выполнять полное сканирование.

person Michael Deardeuff    schedule 11.05.2016
comment
Спасибо за подробный ответ! Я пойду читать о согласованном хэшировании Jump. - person John Hart; 14.05.2016