Подтвердить что ты не робот

Какова самая быстрая хэш-функция для указателей?

Контейнеры на основе хэш-таблицы - это очень быстрый ассоциативный массив (например, unordered_map, unordered_set).

Их производительность сильно зависит от этой хэш-функции, используемой для создания индекса для каждой записи. По мере роста хеш-таблиц элементы снова и снова повторяются.

Указатели - это простой тип, в основном значение 4/8 байт, которое однозначно идентифицирует объект. Проблема в том, что использование адреса в результате хэш-функции неэффективно из-за того, что несколько LSB равны нулю.

Пример:

struct MyVoidPointerHash {
    size_t operator()(const void* val) const {
        return (size_t)val;
    }
};

Более быстрая реализация - потерять несколько бит:

struct MyVoidPointerHash2 {
    size_t operator()(const void* val) const {
        return ((size_t)val) >> 3; // 3 on 64 bit, 1 on 32 bit
    }
};

Последнее произвело увеличение производительности на 10-20% для большого приложения, которое использует хэш-множества и карты с десятками тысяч элементов, которые часто создаются и очищаются.

Может ли кто-нибудь предложить лучшую схему для указателей хеширования?

Функция должна быть:

  • Быстро! и должен хорошо встраиваться.
  • Предлагаем разумное распределение, разрешены редкие столкновения.

Обновление - результаты тестов

Я выполнил два набора тестов, один для int* и для указателя класса, размер которого составляет 4 КБ. Результаты очень интересны.

Я использовал std::unordered_set для всех тестов с размером данных, равным 16 МБ, который был выделен в одном вызове new. Первый алгоритм работал дважды, чтобы убедиться, что кеши настолько горячие, насколько это возможно, и процессор работает на полной скорости.

Настройка: VS2013 (x64), i7-2600, Windows 8.1 x64.

  • VS2013 хэш-функция по умолчанию
  • Hash1: return (size_t)(val);
  • Hash2: return '(size_t)(val) >> 3;
  • Hash3 (@BasileStarynkevitch): uintptr_t ad = (uintptr_t)val; return (size_t)((13 * ad) ^ (ad >> 15));
  • Hash4 (@Roddy): uintptr_t ad = (uintptr_t)val; return (size_t)(ad ^ (ad >> 16));
  • Hash5 (@egur):

код:

template<typename Tval>
struct MyTemplatePointerHash1 {
    size_t operator()(const Tval* val) const {
        static const size_t shift = (size_t)log2(1 + sizeof(Tval));
        return (size_t)(val) >> shift;
    }
};

Тест 1 - int*:

  • VS2013 по умолчанию занял 1292мс
  • Hash1 взял 742ms
  • Hash2 взял 343ms
  • Hash3 взял 1008 мс
  • Hash4 взял 629ms
  • Hash5 взял 350 мс

Тест 1 - 4K_class*:

  • VS2013 по умолчанию занял 0.423мс
  • Hash1 занял 23.889мс
  • Hash2 занял 6.331мс
  • Hash3 взял 0.366мс
  • Hash4 взял 0.390ms
  • Hash5 взял 0.290ms

Update2:

Победителем пока является шаблонная хэш-функция (Hash5). Лучший уровень производительности для скорости для различных размеров блоков.

Обновление 3: Добавлена ​​хэш-функция по умолчанию для базовой линии. Оказывается, это далеко не оптимально.

4b9b3361

Ответ 1

Предоставив этот вопрос на некоторое время, я покажу свою лучшую хэш-функцию для указателей:

template<typename Tval>
struct MyTemplatePointerHash1 {
    size_t operator()(const Tval* val) const {
        static const size_t shift = (size_t)log2(1 + sizeof(Tval));
        return (size_t)(val) >> shift;
    }
};

Это высокая производительность для различных размеров блоков.
Если у кого-то есть лучшая функция, я изменю принятый ответ.

Ответ 2

Правильный ответ с теоретической точки зрения: "Используйте std::hash, который, вероятно, специализируется так же хорошо, как и он, и если это не применимо, используйте хорошую хэш-функцию, а не быструю. скорость хеш-функции не имеет значения, так как ее качество".

Практический ответ: "Используйте std::hash, который является бедным, но тем не менее работает на удивление хорошо".

TL; DR
После того, как я стал заинтригован, я провел около 30 часов тестов за выходные. Среди прочего, я попытался получить средний случай против худшего случая и попытался принудить std::unordered_map к худшему поведению, дав ему намеренно плохие намеки на подсчет веток в отношении установленного размера.

Я сравнивал бедные хэши (std::hash<T*>) с общеизвестными хэшами общего назначения общего хорошего качества (djb2, sdbm), а также с изменениями из них, которые учитывают очень короткую длину ввода, и к хэшам, которые явно считаются для использования в хэш-таблицах (murmur2 и murmur3), а также хеши-бедняки, которые на самом деле хуже, чем не хеширование вообще, поскольку они выбрасывают энтропию.
Поскольку самые низкие 2-3 бита всегда равны нулю в указателях из-за выравнивания, я счел целесообразным проверить простой сдвиг вправо как "хеш", поэтому будет использоваться только ненулевая информация, если хэш-таблица, например. используются только младшие N бит. Оказалось, что для разумных сдвигов (я также пробовал необоснованные сдвиги!) Это действительно хорошо работает.

Выводы

Некоторые из моих выводов были хорошо известны и неудивительны, другие очень удивительны:

  • Трудно предсказать, что такое "хороший" хеш. Написание хороших хеш-функций сложно. Не удивительно, хорошо известный факт и еще раз доказано.
  • Ни один хэш не превзойдет всех остальных в каждом сценарии. Ни один хэш даже не превосходит всех остальных в 80% случаев. Первый результат ожидался, второй, тем не менее, удивительный.
  • Тяжело нажимать std::unordered_map на плохое поведение. Даже если умышленно плохие намеки на подсчет веток, которые заставят несколько повторных хэшей, общая производительность не намного хуже. Только самые худшие хеш-функции, которые отбрасывают большую часть энтропии почти смехотворным образом, способны значительно влиять на производительность более чем на 10-20% (например, right_shift_12, что практически приводит к 12 только хэш-значениям для 50 000 входов Неудивительно, что хеш-карта в этом случае работает в 100 раз медленнее - мы в основном выполняем поиск по произвольному доступу в связанном списке.).
  • Некоторые "забавные" результаты, безусловно, связаны с деталями реализации. Моя реализация (GCC) использует подсчет простого количества больших и больших значений, чем 2 ^ N, и вставляет значения с помощью указательных хэшей вначале в связанные списки.
  • Специализация std::hash<T*> является откровенно жалкой для GCC (простой reinterpret_cast). Смешно, функтор, который делает то же самое, последовательно работает быстрее при вставках и медленнее при произвольном доступе. Разница небольшая (дюжина миллисекунд при 8-10-секундном тестовом прогоне), но это не шум, она постоянно присутствует - вероятно, связана с переупорядочением команд или конвейерной обработкой. Поразительно, как точно такой же код (который также является не-оператором) последовательно выполняет по-разному в двух разных сценариях.
  • Патетические хеши не выполняют значительно хуже, чем "хорошие" хэши или хэши, явно предназначенные для хеш-таблиц. Действительно, половина времени, они лучшие исполнители, или среди лучших 3.
  • "Лучшие" хеш-функции редко, если вообще приводят к лучшей общей производительности.
  • Хеши, опубликованные как ответы в этом вопросе SO, как правило, в порядке. Они хорошие средние, но не превосходят std::hash. Обычно они приземляются в верхней части 3-4.
  • Бедные хэши несколько уязвимы для порядка вставки (хуже при случайной вставке и случайном поиске после случайной вставки), тогда как "хорошие" хэши более устойчивы к влиянию порядка вставки (мало или вообще не отличаются), но общая производительность все еще немного медленнее.

Настройка тестирования

Тестирование проводилось не только с 4-байтовыми или 8-байтовыми (или любыми) выровненными значениями, но и для фактических адресов, получаемых из распределения полного набора элементов в куче, и хранения адресов, предоставленных распределителем в std::vector (объекты были удалены, они не нужны).
Адреса были вставлены во вновь выделенный std::unordered_map для каждого теста в порядке, хранящемся в векторе, один раз в исходном порядке ( "последовательный" ) и один раз после применения a std::random_shuffle на векторе.

Тесты выполнялись для наборов из 50 000 и 1 000 000 объектов размером 4, 16, 64, 256 и 1024 (результаты для 64 опущены здесь для краткости, они как и ожидалось где-то посередине между 16 и 256 - StackOverflow позволяет отправлять только 30 тыс. Символов).
Набор тестов выполнялся 3 раза, результаты варьировались на 3 или 4 миллисекунды здесь и там, но в целом одинаковы. Результаты, опубликованные здесь, являются последним прогоном.

Порядок вставки в "случайном" тесте, а также шаблон доступа (в каждом тесте) является псевдослучайным, но точно идентичным для каждой хэш-функции в тестовом прогоне.

Тайминги в хеш-тестах предназначены для суммирования 4 000 000 000 хэш-значений в целочисленной переменной.

Столбец insert - это время в миллисекундах для 50 итераций создания std::unordered_map, вставляя соответственно 50 000 и 1,000,000 элементов и уничтожая карту.

Столбец access - это время в миллисекундах, чтобы выполнить 100 000 000 поисков псевдослучайного элемента в "векторе", за которым следует поиск этого адреса в unordered_map.
Это время включает в себя в среднем один тайник кэша для доступа к случайному элементу в vector, по крайней мере для большого набора данных (небольшой набор данных полностью помещается в L2).

Все тайминги на 2,66 ГГц Intel Core2, Windows 7, gcc 4.8.1/MinGW-w64_32. Гранулярность таймера @1 мс.

Исходный код

Исходный код доступен в Ideone, опять же из-за ограничения количества символов Stackoverflow 30k.

Примечание. Запуск полного набора тестов занимает более 2 часов на настольном ПК, поэтому будьте готовы совершить прогулку, если хотите воспроизвести результаты.

Результаты испытаний

Benchmarking hash funcs...
std::hash                 2576      
reinterpret_cast          2561      
djb2                      13970     
djb2_mod                  13969     
sdbm                      10246     
yet_another_lc            13966     
murmur2                   11373     
murmur3                   15129     
simple_xorshift           7829      
double_xorshift           13567     
right_shift_2             5806      
right_shift_3             5866      
right_shift_4             5705      
right_shift_5             5691      
right_shift_8             5795      
right_shift_12            5728      
MyTemplatePointerHash1    5652      
BasileStarynkevitch       4315      

--------------------------------
sizeof(T)       = 4
sizeof(T*)      = 4
insertion order = sequential

dataset size    =    50000 elements
name                      insert     access    
std::hash                 421        6988       
reinterpret_cast          408        7083       
djb2                      451        8875       
djb2_mod                  450        8815       
sdbm                      455        8673       
yet_another_lc            443        8292       
murmur2                   478        9006       
murmur3                   490        9213       
simple_xorshift           460        8591       
double_xorshift           477        8839       
right_shift_2             416        7144       
right_shift_3             422        7145       
right_shift_4             414        6811       
right_shift_5             425        8006       
right_shift_8             540        11787      
right_shift_12            1501       49604      
MyTemplatePointerHash1    410        7138       
BasileStarynkevitch       445        8014       

--------------------------------
sizeof(T)       = 4
sizeof(T*)      = 4
insertion order = random

dataset size    =    50000 elements
name                      insert     access    
std::hash                 443        7570       
reinterpret_cast          436        7658       
djb2                      473        8791       
djb2_mod                  472        8766       
sdbm                      472        8817       
yet_another_lc            458        8419       
murmur2                   479        9005       
murmur3                   491        9205       
simple_xorshift           464        8591       
double_xorshift           476        8821       
right_shift_2             441        7724       
right_shift_3             440        7716       
right_shift_4             450        8061       
right_shift_5             463        8653       
right_shift_8             649        16320      
right_shift_12            3052       114185     
MyTemplatePointerHash1    438        7718       
BasileStarynkevitch       453        8140       

--------------------------------
sizeof(T)       = 4
sizeof(T*)      = 4
insertion order = sequential

dataset size    =  1000000 elements
name                      insert     access    
std::hash                 8945       32801      
reinterpret_cast          8796       33251      
djb2                      11139      54855      
djb2_mod                  11041      54831      
sdbm                      11459      36849      
yet_another_lc            14258      57350      
murmur2                   16300      39024      
murmur3                   16572      39221      
simple_xorshift           14930      38509      
double_xorshift           16192      38762      
right_shift_2             8843       33325      
right_shift_3             8791       32979      
right_shift_4             8818       32510      
right_shift_5             8775       30436      
right_shift_8             10505      35960      
right_shift_12            30481      91350      
MyTemplatePointerHash1    8800       33287      
BasileStarynkevitch       12885      37829      

--------------------------------
sizeof(T)       = 4
sizeof(T*)      = 4
insertion order = random

dataset size    =  1000000 elements
name                      insert     access    
std::hash                 12183      33424      
reinterpret_cast          12125      34000      
djb2                      22693      51255      
djb2_mod                  22722      51266      
sdbm                      15160      37221      
yet_another_lc            24125      51850      
murmur2                   16273      39020      
murmur3                   16587      39270      
simple_xorshift           16031      38628      
double_xorshift           16233      38757      
right_shift_2             11181      33896      
right_shift_3             10785      33660      
right_shift_4             10615      33204      
right_shift_5             10357      38216      
right_shift_8             15445      100348     
right_shift_12            73773      1044919    
MyTemplatePointerHash1    11091      33883      
BasileStarynkevitch       15701      38092      

--------------------------------
sizeof(T)       = 64
sizeof(T*)      = 4
insertion order = sequential

dataset size    =    50000 elements
name                      insert     access    
std::hash                 415        8243       
reinterpret_cast          422        8321       
djb2                      445        8730       
djb2_mod                  449        8696       
sdbm                      460        9439       
yet_another_lc            455        9003       
murmur2                   475        9109       
murmur3                   482        9313       
simple_xorshift           463        8694       
double_xorshift           465        8900       
right_shift_2             416        8402       
right_shift_3             418        8405       
right_shift_4             423        8366       
right_shift_5             421        8347       
right_shift_8             453        9195       
right_shift_12            666        18008      
MyTemplatePointerHash1    433        8191       
BasileStarynkevitch       466        8443       

--------------------------------
sizeof(T)       = 64
sizeof(T*)      = 4
insertion order = random

dataset size    =    50000 elements
name                      insert     access    
std::hash                 450        8135       
reinterpret_cast          457        8208       
djb2                      470        8736       
djb2_mod                  476        8698       
sdbm                      483        9420       
yet_another_lc            476        8953       
murmur2                   481        9089       
murmur3                   486        9283       
simple_xorshift           466        8663       
double_xorshift           468        8865       
right_shift_2             456        8301       
right_shift_3             456        8302       
right_shift_4             453        8337       
right_shift_5             457        8340       
right_shift_8             505        10379      
right_shift_12            1099       34923      
MyTemplatePointerHash1    464        8226       
BasileStarynkevitch       466        8372       

--------------------------------
sizeof(T)       = 64
sizeof(T*)      = 4
insertion order = sequential

dataset size    =  1000000 elements
name                      insert     access    
std::hash                 9548       35362      
reinterpret_cast          9635       35869      
djb2                      10668      37339      
djb2_mod                  10763      37311      
sdbm                      11126      37145      
yet_another_lc            11597      39944      
murmur2                   16296      39029      
murmur3                   16432      39280      
simple_xorshift           16066      38645      
double_xorshift           16108      38778      
right_shift_2             8966       35953      
right_shift_3             8916       35949      
right_shift_4             8973       35504      
right_shift_5             8941       34997      
right_shift_8             9356       31233      
right_shift_12            13831      45799      
MyTemplatePointerHash1    8839       31798      
BasileStarynkevitch       15349      38223      

--------------------------------
sizeof(T)       = 64
sizeof(T*)      = 4
insertion order = random

dataset size    =  1000000 elements
name                      insert     access    
std::hash                 14756      36237      
reinterpret_cast          14763      36918      
djb2                      15406      38771      
djb2_mod                  15551      38765      
sdbm                      14886      37078      
yet_another_lc            15700      40290      
murmur2                   16309      39024      
murmur3                   16432      39381      
simple_xorshift           16177      38625      
double_xorshift           16073      38750      
right_shift_2             14732      36961      
right_shift_3             14170      36965      
right_shift_4             13687      37295      
right_shift_5             11978      35135      
right_shift_8             11498      46930      
right_shift_12            25845      268052     
MyTemplatePointerHash1    10150      32046      
BasileStarynkevitch       15981      38143      

--------------------------------
sizeof(T)       = 256
sizeof(T*)      = 4
insertion order = sequential

dataset size    =    50000 elements
name                      insert     access    
std::hash                 432        7957       
reinterpret_cast          429        8036       
djb2                      462        8970       
djb2_mod                  453        8884       
sdbm                      460        9110       
yet_another_lc            466        9015       
murmur2                   495        9147       
murmur3                   494        9300       
simple_xorshift           479        8792       
double_xorshift           477        8948       
right_shift_2             430        8120       
right_shift_3             429        8132       
right_shift_4             432        8196       
right_shift_5             437        8324       
right_shift_8             425        8050       
right_shift_12            519        11291      
MyTemplatePointerHash1    425        8069       
BasileStarynkevitch       468        8496       

--------------------------------
sizeof(T)       = 256
sizeof(T*)      = 4
insertion order = random

dataset size    =    50000 elements
name                      insert     access    
std::hash                 462        7956       
reinterpret_cast          456        8046       
djb2                      490        9002       
djb2_mod                  483        8905       
sdbm                      482        9116       
yet_another_lc            492        8982       
murmur2                   492        9120       
murmur3                   492        9276       
simple_xorshift           477        8761       
double_xorshift           477        8903       
right_shift_2             458        8116       
right_shift_3             459        8124       
right_shift_4             462        8281       
right_shift_5             463        8370       
right_shift_8             458        8069       
right_shift_12            662        16244      
MyTemplatePointerHash1    459        8091       
BasileStarynkevitch       472        8476       

--------------------------------
sizeof(T)       = 256
sizeof(T*)      = 4
insertion order = sequential

dataset size    =  1000000 elements
name                      insert     access    
std::hash                 9756       34368      
reinterpret_cast          9718       34897      
djb2                      10935      36894      
djb2_mod                  10820      36788      
sdbm                      11084      37857      
yet_another_lc            11125      37996      
murmur2                   16522      39078      
murmur3                   16461      39314      
simple_xorshift           15982      38722      
double_xorshift           16151      38868      
right_shift_2             9611       34997      
right_shift_3             9571       35006      
right_shift_4             9135       34750      
right_shift_5             8978       32878      
right_shift_8             8688       30276      
right_shift_12            10591      35827      
MyTemplatePointerHash1    8721       30265      
BasileStarynkevitch       15524      38315      

--------------------------------
sizeof(T)       = 256
sizeof(T*)      = 4
insertion order = random

dataset size    =  1000000 elements
name                      insert     access    
std::hash                 14169      36078      
reinterpret_cast          14096      36637      
djb2                      15373      37492      
djb2_mod                  15279      37438      
sdbm                      15531      38247      
yet_another_lc            15924      38779      
murmur2                   16524      39109      
murmur3                   16422      39280      
simple_xorshift           16119      38735      
double_xorshift           16136      38875      
right_shift_2             14319      36692      
right_shift_3             14311      36776      
right_shift_4             13932      35682      
right_shift_5             12736      34530      
right_shift_8             9221       30663      
right_shift_12            15506      98465      
MyTemplatePointerHash1    9268       30697      
BasileStarynkevitch       15952      38349      

--------------------------------
sizeof(T)       = 1024
sizeof(T*)      = 4
insertion order = sequential

dataset size    =    50000 elements
name                      insert     access    
std::hash                 421        7863       
reinterpret_cast          419        7953       
djb2                      457        8983       
djb2_mod                  455        8927       
sdbm                      445        8609       
yet_another_lc            446        8892       
murmur2                   492        9090       
murmur3                   507        9294       
simple_xorshift           467        8687       
double_xorshift           472        8872       
right_shift_2             432        8009       
right_shift_3             432        8014       
right_shift_4             435        7998       
right_shift_5             442        8099       
right_shift_8             432        7914       
right_shift_12            462        8911       
MyTemplatePointerHash1    426        7744       
BasileStarynkevitch       467        8417       

--------------------------------
sizeof(T)       = 1024
sizeof(T*)      = 4
insertion order = random

dataset size    =    50000 elements
name                      insert     access    
std::hash                 452        7948       
reinterpret_cast          456        8018       
djb2                      489        9037       
djb2_mod                  490        8992       
sdbm                      477        8795       
yet_another_lc            491        9179       
murmur2                   502        9078       
murmur3                   507        9273       
simple_xorshift           473        8671       
double_xorshift           480        8873       
right_shift_2             470        8105       
right_shift_3             470        8100       
right_shift_4             476        8333       
right_shift_5             468        8065       
right_shift_8             469        8094       
right_shift_12            524        10216      
MyTemplatePointerHash1    451        7826       
BasileStarynkevitch       472        8419       

--------------------------------
sizeof(T)       = 1024
sizeof(T*)      = 4
insertion order = sequential

dataset size    =  1000000 elements
name                      insert     access    
std::hash                 10910      38432      
reinterpret_cast          10892      38994      
djb2                      10499      38985      
djb2_mod                  10507      38983      
sdbm                      11318      37450      
yet_another_lc            11740      38260      
murmur2                   16960      39544      
murmur3                   16816      39647      
simple_xorshift           16096      39021      
double_xorshift           16419      39183      
right_shift_2             10219      38909      
right_shift_3             10012      39036      
right_shift_4             10642      40284      
right_shift_5             10116      38678      
right_shift_8             9083       32914      
right_shift_12            9376       31586      
MyTemplatePointerHash1    8777       30129      
BasileStarynkevitch       16036      38913      

--------------------------------
sizeof(T)       = 1024
sizeof(T*)      = 4
insertion order = random

dataset size    =  1000000 elements
name                      insert     access    
std::hash                 16304      38695      
reinterpret_cast          16241      39641      
djb2                      16377      39533      
djb2_mod                  16428      39526      
sdbm                      15402      37811      
yet_another_lc            16180      38730      
murmur2                   16679      39355      
murmur3                   16792      39586      
simple_xorshift           16196      38965      
double_xorshift           16371      39127      
right_shift_2             16445      39263      
right_shift_3             16598      39421      
right_shift_4             16378      39839      
right_shift_5             15517      38442      
right_shift_8             11589      33547      
right_shift_12            11992      49041      
MyTemplatePointerHash1    9052       30222      
BasileStarynkevitch       16163      38829      

Ответ 3

Результат, возвращаемый хэш-функцией, имеет тип size_t, но он преобразуется в "индекс ведра" контейнером, идентифицируя правильное ведро для поиска объекта.

Я думаю, что это преобразование не указано в стандарте: но я бы ожидал, что это обычно операция Modulo N, где N - количество ведер - и что N обычно имеет силу в два раза, так как удваивает количество ковша это хороший способ увеличить размер, когда слишком много хитов. Операция Modulo N будет означать, что - для указателей - наивная хэш-функция использует только часть ковшей.

Реальная проблема заключается в том, что "хороший" хэш-алгоритм для контейнеров должен основываться на знании размера ведра и значений, которые вы хешируете. Например, если объекты, которые вы хранили в таблице, имели размер 1024 байта, возможно, что младшие 10 бит каждого указателя могут быть одинаковыми.

struct MyOneKStruct x[100];  //bottom 10 bits of &x[n] are always the same

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

Однако вместо того, чтобы просто сдвигать указатель вниз на N бит, я бы попробовал что-то вроде XORing верхнего слова в нижнем. Как @BasileStarynkevich ответ.

Предложение о добавлении хеш-таблиц делает интересное чтение. Мой акцент в пункте ниже: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1456.html

Невозможно написать полностью общую хеш-функцию, которая действительна для всех типов. (Вы не можете просто преобразовать объект в необработанную память и хэш-байты; среди прочего, эта идея терпит неудачу из-за padding.) Из-за этого, а также потому, что хорошая хеш-функция только хорошо в контексте конкретной модели использования, это важно чтобы пользователи могли предоставлять свои собственные хеш-функции.

Ответ 4

Очевидно, что ответ зависит от системы и процессора (в частности, из-за размера страницы и размера слова). Я предлагаю

  struct MyVoidPointerHash {
      size_t operator()(const void* val) const {
         uintptr_t ad = (uintptr_t) val;
         return (size_t) ((13*ad) ^ (ad >> 15));
      }
  };

Понимание заключается в том, что во многих системах размер страницы часто составляет 4 Кбайта (т.е. 2 12), поэтому правый сдвиг >>15 помещает значимые части адреса в младшие биты. 13* в основном для удовольствия (но 13 является простым) и перетасовывать больше бит. Эксклюзивный или ^ - это биты смешения и очень быстрые. Таким образом, младшие биты хэша представляют собой смесь многих бит (как высоких, так и низких) указателя.

Я не утверждаю, что поставил много "науки" в такие хэш-функции. Но они часто работают очень хорошо. YMMV. Я бы предположил, что вам следует избегать деактивации ASLR!

Ответ 5

Невозможно побить ваше решение на беговой дорожке производительности (ни для char, ни для 1024 struct), но в смысле правильности есть некоторые улучшения:

#include <iostream>
#include <new>
#include <algorithm>
#include <unordered_set>
#include <chrono>

#include <cstdlib>
#include <cstdint>
#include <cstddef>
#include <cmath>

namespace
{

template< std::size_t argument, std::size_t base = 2, bool = (argument < base) >
constexpr std::size_t log = 1 + log< argument / base, base >;

template< std::size_t argument, std::size_t base >
constexpr std::size_t log< argument, base, true > = 0;

}

struct pointer_hash
{

    template< typename type >
    constexpr
    std::size_t
    operator () (type * p) const noexcept
    {
        return static_cast< std::size_t >(reinterpret_cast< std::uintptr_t >(p) >> log< std::max(sizeof(type), alignof(type)) >);
    }

};

template< typename type = std::max_align_t, std::size_t i = 0 >
struct alignas(alignof(type) << i) S
{

};

int
main()
{
    constexpr std::size_t _16M = (1 << 24);
    S<> * p = new S<>[_16M]{};
    auto latch = std::chrono::high_resolution_clock::now();
    {
        std::unordered_set< S<> *, pointer_hash > s;
        for (auto * pp = p; pp < p + _16M; ++pp) {
            s.insert(pp);
        }
    }
    std::cout << std::chrono::duration_cast< std::chrono::milliseconds >(std::chrono::high_resolution_clock::now() - latch).count() << "ms" << std::endl;
    delete [] p;
    return EXIT_SUCCESS;
}