Мы разрабатываем высокопроизводительное критически важное программное обеспечение на C ++. Там нам нужна параллельная хеш-карта и реализованная. Итак, мы написали тест, чтобы выяснить, насколько медленнее наша параллельная хэш-карта по сравнению с std::unordered_map
.
Но, std::unordered_map
кажется, невероятно медленно ... Так что это наш микро-тест (для одновременного отображения мы породили новую нить , чтобы убедиться , что сделать замок не получить оптимизированный прочь и к сведению , что я никогда не INSER 0 , потому что я также тест с google::dense_hash_map
, которому требуется нулевое значение):
boost::random::mt19937 rng;
boost::random::uniform_int_distribution<> dist(std::numeric_limits<uint64_t>::min(), std::numeric_limits<uint64_t>::max());
std::vector<uint64_t> vec(SIZE);
for (int i = 0; i < SIZE; ++i) {
uint64_t val = 0;
while (val == 0) {
val = dist(rng);
}
vec[i] = val;
}
std::unordered_map<int, long double> map;
auto begin = std::chrono::high_resolution_clock::now();
for (int i = 0; i < SIZE; ++i) {
map[vec[i]] = 0.0;
}
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "inserts: " << elapsed.count() << std::endl;
std::random_shuffle(vec.begin(), vec.end());
begin = std::chrono::high_resolution_clock::now();
long double val;
for (int i = 0; i < SIZE; ++i) {
val = map[vec[i]];
}
end = std::chrono::high_resolution_clock::now();
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "get: " << elapsed.count() << std::endl;
(РЕДАКТИРОВАТЬ: весь исходный код можно найти здесь: http://pastebin.com/vPqf7eya )
Результат для std::unordered_map
:
inserts: 35126
get : 2959
Для google::dense_map
:
inserts: 3653
get : 816
Для нашей поддерживаемой вручную параллельной карты (которая выполняет блокировку, хотя тест является однопоточным, но в отдельном потоке создания):
inserts: 5213
get : 2594
Если я скомпилирую тестовую программу без поддержки pthread и запустил все в основном потоке, я получу следующие результаты для нашей параллельной карты с ручной поддержкой:
inserts: 4441
get : 1180
Я компилирую с помощью следующей команды:
g++-4.7 -O3 -DNDEBUG -I/tmp/benchmap/sparsehash-2.0.2/src/ -std=c++11 -pthread main.cc
Так что особенно вставки на std::unordered_map
кажутся чрезвычайно дорогими - 35 секунд против 3-5 секунд на других картах. Также время поиска кажется довольно большим.
Мой вопрос: почему это? Я прочитал еще один вопрос о stackoverflow, где кто-то спрашивает, почему std::tr1::unordered_map
он медленнее, чем его собственная реализация. Там самый высокий ответ гласит, что std::tr1::unordered_map
необходимо реализовать более сложный интерфейс. Но я не вижу этого аргумента: мы используем подход ведра в нашем concurrent_map, также std::unordered_map
используется подход ведра ( google::dense_hash_map
нет, но чем он std::unordered_map
должен быть, по крайней мере, таким же быстрым, как наша безопасная для параллелизма версия с ручной поддержкой?). Кроме того, я не вижу в интерфейсе ничего, что заставляет функцию, которая заставляет хэш-карту работать плохо ...
Итак, мой вопрос: правда ли, что это std::unordered_map
кажется очень медленным? Если нет: что не так? Если да, то с чем это связано.
И мой главный вопрос: почему вставлять значение в std::unordered_map
такую ужасно дорогостоящую (даже если мы зарезервируем достаточно места в начале, это не будет работать намного лучше - поэтому перефразирование, похоже, не проблема)?
РЕДАКТИРОВАТЬ:
Прежде всего: да, представленный тест не безупречен - это потому, что мы много играли с ним, и это всего лишь хак (например, uint64
распределение для генерации целых чисел на практике не было бы хорошей идеей, исключите 0 в цикле это глупо и т.д ...).
На данный момент большинство комментариев объясняют, что я могу сделать unordered_map быстрее, предварительно выделив для него достаточно места. В нашем приложении это просто невозможно: мы разрабатываем систему управления базой данных, и нам нужна хеш-карта для хранения некоторых данных во время транзакции (например, информации о блокировках). Таким образом, эта карта может быть чем угодно, от 1 (пользователь просто делает одну вставку и фиксирует) до миллиардов записей (если происходит полное сканирование таблицы). Здесь просто невозможно заранее выделить достаточно места (и простое выделение большого количества вначале потребует слишком много памяти).
Кроме того, я прошу прощения, что я недостаточно четко сформулировал свой вопрос: я не очень заинтересован в быстром unordered_map (использование плотной хэш-карты googles отлично работает для нас), я просто не совсем понимаю, откуда берутся эти огромные различия в производительности . Это не может быть просто предварительное выделение (даже при достаточном количестве предварительно выделенной памяти плотная карта на порядок быстрее, чем unordered_map, наша параллельная карта, поддерживаемая вручную, начинается с массива размером 64 - поэтому он меньше, чем unordered_map).
Так в чем же причина такой плохой работы std::unordered_map
? Или другой вопрос: можно ли написать реализацию std::unordered_map
интерфейса, которая соответствует стандарту и (почти) так же быстро, как плотная хеш-карта googles? Или в стандарте есть что-то, что заставляет разработчика выбирать неэффективный способ его реализации?
РЕДАКТИРОВАТЬ 2:
Путем профилирования я вижу, что много времени уходит на целочисленные деления. std::unordered_map
использует простые числа для размера массива, в то время как другие реализации используют степень двойки. Почему std::unordered_map
используются простые числа? Чтобы работать лучше, если хеш плохой? Для хороших хешей это не имеет значения.
РЕДАКТИРОВАТЬ 3:
Это числа для std::map
:
inserts: 16462
get : 16978
Таааааааааааааааааааааааааааа более йлатой)): почему вставки в a std::map
быстрее, чем вставки в std::unordered_map
... Я про WAT? std::map
имеет худшую локальность (дерево против массива), ему нужно делать больше распределений (за вставку против за повтор + плюс ~ 1 для каждого столкновения) и, что наиболее важно: имеет другую алгоритмическую сложность (O (logn) против O (1))!
SIZE
.Ответы:
Я нашел причину: это проблема gcc-4.7 !!
С gcc-4.7
С gcc-4.6
Итак,
std::unordered_map
в gcc-4.7 не работает (или моя установка, которая является установкой gcc-4.7.0 на Ubuntu - и другой установкой, которая является gcc 4.7.1 при тестировании debian).Я отправлю отчет об ошибке ... до тех пор: НЕ используйте
std::unordered_map
с gcc 4.7!источник
max_load_factor
обработке, которые привели к разнице в производительности.Я предполагаю, что вы неправильно
unordered_map
выбрали размер , как предложил Илизар. Когда цепочки становятся слишком длиннымиunordered_map
, реализация g ++ автоматически перекэшируется в более крупную хеш-таблицу, и это сильно снижает производительность. Если я правильно помню, поunordered_map
умолчанию (наименьшее простое число больше)100
.У меня не было
chrono
в моей системе, поэтому я рассчиталtimes()
.Я использовал
SIZE
оф10000000
, и мне пришлось немного изменить кое-что для моей версииboost
. Также обратите внимание, что я предварительно определил размер хеш-таблицы, чтобы она соответствовалаSIZE/DEPTH
, гдеDEPTH
это оценка длины цепочки ведра из-за хеш-коллизий.Изменить: Ховард указывает мне в комментариях, что максимальный коэффициент нагрузки
unordered_map
составляет1
. Итак,DEPTH
контролируется, сколько раз код будет перефразироваться.Редактировать:
Я изменил код, чтобы мне было
DEPTH
легче изменить его .Итак, по умолчанию для хеш-таблицы выбирается худший размер.
Я пришел к выводу, что нет большой разницы в производительности для любого начального размера хеш-таблицы, кроме как сделать его равным всему ожидаемому количеству уникальных вставок. Кроме того, я не вижу той разницы в производительности, которую вы наблюдаете.
источник
std::unordered_map
по умолчанию имеет максимальный коэффициент загрузки 1. Таким образом, за исключением начального количества ковшей, ваша ГЛУБИНА игнорируется. При желании можноmap.max_load_factor(DEPTH)
.DEPTH
игнорируется, но он по-прежнему определяет, как часто карта будет перефразирована в карту большего размера. Ответ был обновлен, и еще раз спасибоSIZE
вы работали. Я могу сказать, чтоunordered_map
это в два раза быстрее сDEPTH
установленным1
и правильно предварительно выделенным.DEPTH
установленным значением1
занимают менее3
секунд, как это на порядок медленнее?Я запустил ваш код на компьютере 64 бит / AMD / 4 ядра (2,1 ГГц) и получил следующие результаты:
MinGW-W64 4.9.2:
Использование std :: unordered_map:
Использование std :: map:
VC 2015 со всеми известными мне флагами оптимизации:
Использование std :: unordered_map:
Использование std :: map:
Я не тестировал код с использованием GCC, но я думаю, что он может быть сопоставим с производительностью VC, поэтому, если это правда, то GCC 4.9 std :: unordered_map все еще не работает.
[РЕДАКТИРОВАТЬ]
Так что да, как кто-то сказал в комментариях, нет оснований полагать, что производительность GCC 4.9.x будет сопоставима с производительностью VC. Когда у меня появятся изменения, я буду тестировать код на GCC.
Мой ответ - просто создать некую базу знаний для других ответов.
источник