Мы разрабатываем высокопроизводительное критически важное программное обеспечение на 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
.