Как реализована карта std:: unordered

C++ unordered_map обработка столкновений, изменение размера и rehash

Это предыдущий вопрос, открытый мной, и я видел, что у меня много путаницы о том, как реализован unordered_map. Я уверен, что многие другие люди разделяют эту путаницу со мной. Основываясь на информации, которую я знаю, не читая стандарт:

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

Я надеялся, что кто-то может объяснить реализацию и как она соответствует стандартному определению c++ (с точки зрения требований к производительности ) и если это действительно не самый эффективный способ реализации структуры данных хэш-карты, как ее можно улучшить ?

1 ответов


стандарт эффективно мандаты std::unordered_set и std::unordered_map реализации, использующие открытое хэширование, что означает массив ведер, каждый из которых содержит головку логического (и обычно фактического) списка. Это требование является тонким: это следствие того, что коэффициент максимальной нагрузки по умолчанию равен 1.0 и гарантия того, что таблица не будет перефразирована, если не превысит этот коэффициент нагрузки: это было бы непрактично без цепочки, так как столкновения с закрытым хэшированием становятся подавляющими, как коэффициент нагрузки приближается к 1:

23.2.5/15: тег insert и emplace члены не влияют на действительность итераторов, если (N+n) < z * B, где N - количество элементов в контейнере до операции вставки n - количество элементов, вставленных, B является количество ведер контейнера, и z коэффициент максимальной нагрузки контейнера.

среди эффекты конструктора в 23.5.4.2/1:max_load_factor() возвращает 1.0.

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

относительно текста, который вы цитируете:

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

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

как свидетельство осознания, от предложение Мэтью Остерна здесь:

Я не знаю ни одного удовлетворительного осуществление открытого рассмотрения в общих рамках. Открытая адресация представляет ряд проблем:

• необходимо отличать вакантную должность от занятой.

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

• Открытая адресация затрудняет управление столкновениями: если вы вставляете элемент, хэш-код которого сопоставляется с уже занятым местоположением, вам нужна политика, которая говорит вам, где попробовать дальше. Это решенная проблема, но наиболее известные решения сложны.

• управление столкновениями особенно сложно, когда допускается стирание элементов. (См. кнут для обсуждения.) Класс контейнера для стандартной библиотеки должен допускать стирание.

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

решение этих проблем может быть интересным исследовательским проектом, но, в отсутствие опыта реализации в контексте C++, было бы нецелесообразно стандартизировать контейнер с открытой адресацией класс.

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

полное сравнение и разработка вариантов дизайна хэш-таблицы и их последствий не по теме для S. O. поскольку это слишком широко, чтобы адрес здесь правильный.