Skip to content

Latest commit

 

History

74 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fast hash table (using AVX2/ASM/SSE)

О проекте

Перед прочтением данной статьи читателю настоятельно рекомендуется изучить:

Целью данного проекта стояла реализация хеш-таблицы закрытого типа и ее оптимизация с помощью Intel intrinsics (SIMD-инструкции), ассемблерных вставок, а также ассемблерных функций. Важно заметить, что хеш-таблица должна быть универсальной - уметь работать с любыми типами ключей и значений.

Этапы выполнения работы и оптимизаций

Первая часть: написание хеш-таблицы

Кратко о хеш-таблице:

За FILL-factor (size of hash table / capacity of hash table) было взято значение 0.72f (такое же, как в хеш-таблице в языке C#). Опыт показал, что значения 0.7-0.75 оптимальны. Так, в языке Java используется значение 0.75f.

За GROWTH-factor было взято значение ~1.61 (1 + (sqrt(5) - 1) / 2), что, доказано математически, оптимально при обьеме данных, стремящемся к бесконечности.

Структуры хеш-таблицы и ее элемента (node_t, таблица построена на односвязных списках) задаются так:

ht_t - хеш-таблица

node_t - элемент односвязного списка

Важно отметить, что типы NODE_VALUE_TYPE и NODE_KEY_TYPE задаются пользователем в специальном файле - types_config.h, что делает настройку хеш-таблицы очень удобной и простой.

Более того, все функции хеширования/сравнения ключей, а также возвращаемые default-параметры ключа и значения также кастомизируются пользователем в файле types_config.

Кратко тексте:

Файл - Текст произведения "Война и Мир", увеличенный в 10 раз (засчет копирования). Количество односвязных списков примерно 21000. Количество рассчитано таким образом, чтобы в каждом списке было примерно 20 элементов. Всего слов в тексте 3.1млн.

Профилирование программы, первая оптимизация

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

Тут приведены результаты работы callgrind над программой, запущенной БЕЗ оптимизаций. Результаты работы callgrind без оптимизаций

Очевидно, что самым важным для оптимизации и "узким" местом программы является функция HASH-6, то есть CRC32, являющаяся default-функцией для нахождения хешей в моей хеш-таблице.

Я решил переписать свою хеш-функцию с использованием Intrinsic-функций (то есть, simd-инструкций). Изменив принцип хранения слов в своем тексте, выравнивая буффер для каждого слова по 32 (на самом деле - кладя все слова в один буффер по адресам, кратным 32), я узнал максимальную длину каждого слова (32 - абсолютный максимум, в выбранном мною тексте - "Война и мир", нет ни одного слова, длиннее 22 букв). Это позволило мне использовать Intrinsic _mm_crc32_u64. Таким образом, время подсчета одного слова ограничивалось 4 итерациями цикла с нахождением crc32 (32 / 4).

Оптимизированная хеш-функция

Результат оптимизации превзошел все мои ожидания! Время работы хеш-функции уменьшилось в десятки раз!

Результат работы callgrind с оптимизированной хеш-функцией:

Вторая оптимизация

Посмотрим на время работы функции, сравнивающей строки:

Strcmp без оптимизаций

После первой оптимизации стало очевидно, что, кроме функций find_node && ht_get узким местом моей программы являлась функция strcmp. Изначально я использовал функцию strcmp из стандартной библиотеки языка C. Однако, благодаря первой выполненной оптимизации, теперь мне точно известная длина слова - 32 байта! Благодаря этому, я смог использовать Intrinsic _mm256_cmpeq_epi8, сравнивающий векторы.

Оптимизированная функция avx_strcmp

Это дало очень приятный результат: время работы strcmp также уменьшилось в десятки раз!

Результат работы callgrind с оптимизированной strcmp

Третья оптимизация, отключение дополнительных проверок и проч.

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

К сожалению, эта оптимизация не дала результатов - общее время работы программы почти не изменилось. Поэтому, приводить здесь результаты работы callgrind не имеет смысла.

Четвёртая часть, подведение итогов

Результат после финальной оптимизации

Как мы видим, оставшиеся функции - ht_get и find_node тратят больше всего место. Однако, дальнейшая оптимизация не так и нужна - благодаря оптимизациям avx_strcmp и HASH_6, время работы программы и без того уменьшилось почти в 2.5 раза. То есть, 43% от текущего времени работы программы - около 17.5% от времени работы исходной программы. Сложность оптимизации этих функций при переписывании на ASM очень (!) высока, к тому же, такая оптимизация сильно ухудшит совместимость. Поэтому, было принято решение не оптимизировать эти функции.

Тестирование хэш-функций

Ниже привожу примеры тестирования количества коллизий в хеш-функциях.

Тестируемые функции

Всего я написал 6 различных хеш-функций:

HASH_1: 1 в любом случае

HASH_2: Возвращаем ASCII-код первого символа

HASH_3: strlen

HASH_4: sum(ascii_codes) всех символов

HASH_5: H_0 = s[0]; H_i = rol(H_{i-1}) + s[i]

HASH_6: CRC32 (default)

Результаты

Огромное количество коллизий, ужасный хеш. H1

Очень много коллизий, использовать такой хеш неоптимально. H2

Тут мы видим более равномерное распределение коллизий, но, все равно, такой вариант не подходит, количество коллизий в значениях X 1000-1500 огромное. H3

Один из худших алгоритмов. В моем тексте strlen(word) <= 22 для любого word. То есть, вместо 2500 мы используем 22 односвязных массива. H4

Первый алгоритм, который успешно показал себя. Количество коллизий в среднем не превышает 40. H5

Лучший из представленных алгоритмов - коллизии распределены равномерно, в среднем не превышает 40. H6

About

Closed-addressing hash table vectorized with Intel AVX2 SIMD intrinsics, hardware CRC32, and cache-line aligned memory.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages