Ми розробляємо високоефективне програмне забезпечення на C ++. Там нам потрібна паралельна хеш-карта та реалізована. Тому ми написали орієнтир, щоб визначити, наскільки повільніше порівняно з нашою паралельною хеш-картою std::unordered_map
.
Але, std::unordered_map
здається, це надзвичайно повільно ... Отже, це наш мікро-орієнтир (для паралельної карти ми породили нову нитку, щоб переконатися, що блокування не буде оптимізовано, і зауважте, що я ніколи не вставляю 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;
(EDIT: весь вихідний код можна знайти тут: 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
розподіл для генерації ints на практиці не було б гарною ідеєю, виключайте 0 у циклі якийсь дурний і т. д.).
На даний момент більшість коментарів пояснює, що я можу зробити не упорядкований_мапу швидше, попередньо виділивши достатньо місця для цього. У нашому додатку це просто неможливо: ми розробляємо систему управління базами даних і потребуємо хеш-карту для зберігання деяких даних під час транзакції (наприклад, блокування інформації). Таким чином, ця карта може бути від 1 (користувач робить лише одну вставку і виконує) до мільярдів записів (якщо відбувається сканування повної таблиці). Тут просто неможливо виділити достатньо місця (і просто виділити багато на початку буде споживати занадто багато пам’яті).
Крім того, прошу вибачення, що я не вказав своє питання досить чітким: я не дуже зацікавлений в тому, щоб зробити не упорядкований_мап швидким (використання густої хеш-карти Google google працює добре для нас), я просто не розумію, звідки беруться такі величезні відмінності в продуктивності . Це не може бути просто попереднім розміщенням (навіть при достатній попередньо розподіленій пам’яті щільна карта на порядок швидше, ніж не упорядкована_мапа, наша рука, що підтримується одночасно, починається з масиву розміром 64 - таким чином меншим, ніж не упорядкованим_мапом).
То в чому причина цієї поганої роботи std::unordered_map
? Або по-іншому запитують: чи можна написати реалізацію std::unordered_map
інтерфейсу, який є стандартним і відповідає (майже) так само швидко, як густа хеш-карта Google? Або є щось у стандарті, що змушує виконавця обрати неефективний спосіб його реалізації?
EDIT 2:
Профілюючи, я бачу, що багато часу використовується для цілих поділів. std::unordered_map
використовує прості числа для розміру масиву, в той час як інші реалізації використовують два потужності. Чому std::unordered_map
використовуються прості числа? Краще працювати, якщо хеш поганий? Для гарних хешей це не має ніякого значення.
EDIT 3:
Це номери для std::map
:
inserts: 16462
get : 16978
Sooooooo: чому вставки std::map
швидше, ніж вставки в std::unordered_map
... я маю на увазі ВАТ? std::map
має гірший населений пункт (дерево проти масиву), потрібно зробити більше виділень (за вставку проти повторного удару + плюс ~ 1 за кожне зіткнення) і, найголовніше: має іншу алгоритмічну складність (O (logn) проти O (1))!
SIZE
.