некоторые вопросы по хеш-таблице / unordered_map

Я работаю над приложением с малой задержкой, которое всегда должно быть высокоэффективным.

Мне нужно найти какой-то индекс на основе строки, поэтому я использую c ++ unordered_map. Ограничения: -Только вставка и поиск, без удаления -key - строка, значение - int -Ожидается, что в unordered_map будет добавлено не более 1 миллиона записей.

  • Я устанавливаю резерв unordered_map на 1 миллион. Это хорошо, или мне следует зарезервировать на порядок на несколько% больше, чем ожидалось, чтобы избежать повторного хеширования? Могу ли я установить его на 1 миллион, или я должен установить большое простое число, близкое к 1 миллиону или около 2 степени.

  • Я использую хеш-функцию строки по умолчанию в c ++ std lib, которая оказывается murmur2. Мои ключи имеют длину от - 25 до 50 символов, и все они являются уникальными ключами, содержащими цифры, прописные буквы английского алфавита и символы _. Достаточно ли этой хэш-функции для равномерного распределения ключей или мне нужно предоставить лучшую хеш-функцию для unordered_map?

  • Будет ли unordered_map выделять пространство для 1 миллиона пар ключей, значений, а также для массива размером 1 миллион, когда я вызываю резерв или резерв, создается только массив этого размера, а пары ключей и значений выделяются динамически при вставке?

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

  • Из соображений производительности было бы неплохо реализовать мою собственную хеш-таблицу с памятью, предварительно выделенной для 1 миллиона записей в стеке или во время инициализации, или вышеупомянутые оптимизации unordered_map достаточно близки?

  • Есть ли способ заранее выделить память для ожидаемого количества записей в unorderd_map, чтобы избежать динамического выделения при вставке?


person Medicine    schedule 21.10.2013    source источник


Ответы (1)


Попробуем ответить на некоторые из этих вопросов с помощью кода. Я не вставляю все это целиком, так как это длинновато. Вы можете найти весь код здесь. Я вставляю часть вывода сюда:

Map without reserve

        size: 0
bucket_count: 23
 load_factor: 0

Allocation count: 0

... 
about 15 reallocations deleted 
...

Allocation count: 1000015

        size: 1000000
bucket_count: 1236397
 load_factor: 0.808802

0: 550454
1: 445645
2: 180174
3: 48593
4: 9708
5: 1568
6: 231
7: 22
8: 2

Map with reserve

        size: 0
bucket_count: 23
 load_factor: 0

Allocation count: 1

        size: 0
bucket_count: 2144977
 load_factor: 0

Allocation count: 1000000

        size: 1000000
bucket_count: 2144977
 load_factor: 0.466205

0: 1346008
1: 626748
2: 146625
3: 22663
4: 2669
5: 248
6: 15
7: 1
  • Как видите, когда вы резервируете место для 1m элементов, происходит только одно распределение. Думаю, это для ведер.
  • Количество зарезервированных сегментов намного превышает 1 метр.
  • Количество размещений точно такое же, как количество вставленных элементов.
  • Распределение хешей, которое вы можете увидеть для каждого случая: довольно много коллизий. Иногда до 8 элементов в ведре, даже если полмиллиона ведер пусто.
  • Без начального reserve происходит около 15 перераспределений по пути, но полученная карта имеет меньше сегментов.
  • С достаточно большим reserve перераспределений нет вообще.
  • Конечно, вы можете свернуть свою собственную хеш-таблицу. Например, вы можете зарезервировать один непрерывный блок пространства для всех ключей, поскольку их длина не превышает 50 байт каждый, а также блок для значений. Но я уверен, что это будет неплохая работа, возможно, без пользы. Профилируйте и запишите выделение памяти, прежде чем вы начнете заново реализовывать то, что, возможно, не должно быть.
person detunized    schedule 21.10.2013
comment
если я установил коэффициент загрузки на 1.0, ведра должны быть равны записям в резерве, правильно? - person Medicine; 22.10.2013
comment
Ты можешь это сделать? Я считаю, что вы можете изменить только max_load_factor, и по умолчанию он равен 1.0. - person detunized; 22.10.2013
comment
Извините, я имел в виду, когда max_load_factor установлен в 1.0, что также по умолчанию, тогда количество сегментов и элементов должно быть одинаковым? Кроме того, когда резерв установлен на 1 миллион элементов, выделяется пространство для соответствующего количества сегментов или выделяется пространство как для сегментов, так и для пар элементов (ключ, значение)? Динамическое выделение памяти - одно из НЕТ при написании приложений с малой задержкой. - person Medicine; 22.10.2013
comment
@Medicine, я посмотрел на реализацию GCC unordered_map, и она умножает количество сегментов на _S_growth_factor (что составляет 2) внутри hashtable_policy.h, а затем берет наименьшее простое число больше, чем результат из предоставленного списка __prime_list. Так вот откуда взялся 2144977. - person detunized; 22.10.2013
comment
@Medicine, предоставив свой собственный распределитель, вы можете контролировать операции выделения памяти. Имеет смысл предварительно выделить узлы корзины пула и просто выделить их оттуда. - person detunized; 22.10.2013