Наиболее эффективная хэш-функция Unicode для Delphi 2009

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

Я изначально начал с Габрфункция HashOf от GpStringHash:

function HashOf(const key: string): cardinal;
asm
  xor edx,edx     { result := 0 }
  and eax,eax     { test if 0 }
  jz @End         { skip if nil }
  mov ecx,[eax-4] { ecx := string length }
  jecxz @End      { skip if length = 0 }
@loop:            { repeat }
  rol edx,2       { edx := (edx shl 2) or (edx shr 30)... }
  xor dl,[eax]    { ... xor Ord(key[eax]) }
  inc eax         { inc(eax) }
  loop @loop      { until ecx = 0 }
@End:
  mov eax,edx     { result := eax }
end; { HashOf }

но я обнаружил, что это не дает хороших чисел из строк Unicode. Я отметил, что процедуры Gabr не были обновлены до Delphi 2009.

затем я обнаружил HashNameMBCS в SysUtils of Delphi 2009 и перевел его на эту простую функцию (где "string" - строка Юникода Delphi 2009):

function HashOf(const key: string): cardinal;
var
  I: integer;
begin
  Result := 0;
  for I := 1 to length(key) do
  begin
    Result := (Result shl 5) or (Result shr 27);
    Result := Result xor Cardinal(key[I]);
  end;
end; { HashOf }

Я думал, что это было довольно хорошо, пока я не посмотрел на окно CPU и не увидел код ассемблера, который он создал:

Process.pas.1649: Result := 0;
0048DEA8 33DB             xor ebx,ebx
Process.pas.1650: for I := 1 to length(key) do begin
0048DEAA 8BC6             mov eax,esi
0048DEAC E89734F7FF       call 401348
0048DEB1 85C0             test eax,eax
0048DEB3 7E1C             jle 48ded1
0048DEB5 BA01000000       mov edx,000001
Process.pas.1651: Result := (Result shl 5) or (Result shr 27);
0048DEBA 8BCB             mov ecx,ebx
0048DEBC C1E105           shl ecx,
0048DEBF C1EB1B           shr ebx,b
0048DEC2 0BCB             or ecx,ebx
0048DEC4 8BD9             mov ebx,ecx
Process.pas.1652: Result := Result xor Cardinal(key[I]);
0048DEC6 0FB74C56FE       movzx ecx,[esi+edx*2-]
0048DECB 33D9             xor ebx,ecx
Process.pas.1653: end;
0048DECD 42               inc edx
Process.pas.1650: for I := 1 to length(key) do begin
0048DECE 48               dec eax
0048DECF 75E9             jnz 48deba
Process.pas.1654: end; { HashOf }
0048DED1 8BC3             mov eax,ebx

Это, кажется, содержит немного больше кода ассемблера, чем код Габра.

скорость. Есть ли что-нибудь, что я могу сделать, чтобы улучшить код pascal, который я написал, или ассемблер, который мой код генерируется?


продолжение.

я, наконец, пошел с функцией HashOf, основанной на SysUtils.HashNameMBCS. Кажется, это дает хороший хэш-дистрибутив для строк Unicode и кажется довольно быстрым.

Да, генерируется много ассемблерного кода, но код Delphi, который генерирует его, настолько прост и использует только операции битового сдвига, что трудно поверить, что это не будет быстро.

4 ответов


выход ASM не является хорошим показателем скорости алгоритма. Кроме того, из того, что я вижу, две части кода выполняют почти одинаковую работу. Самой большой разницей, по-видимому, является стратегия доступа к памяти, а первая-использование roll-left вместо эквивалентного набора инструкций (shl | shr-большинство языков программирования более высокого уровня оставляют операторы "roll"). Последнее может быть лучше, чем первое.

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

конечно,benchmark и выбрать победителя. Если вам нравится вывод второго, но первый быстрее, подключите значения второго к первому.

rol edx,5 { edx := (edx shl 5) or (edx shr 27)... }

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


PS. Я не уверен, что этот алгоритм создает равномерное распределение, то, что вы явно вызвали (вы запустили гистограммы?). Вы можете посмотреть портирование эта хэш-функция в Дельфы. Это может быть не так быстро, как приведенный выше алгоритм, но он кажется довольно быстрым, а также дает хорошее распределение. Опять же, мы, вероятно, речь идет о разнице порядка миллисекунд над мегабайтами данных.


некоторое время назад мы провели небольшой конкурс, улучшив хэш под названием"MurmurHash"; Цитата Из Википедии:

Он отметил, необычайной быстрое, часто в два-четыре раза быстрее чем сопоставимые алгоритмы, такие как FNV, Jenkins ' lookup3 и Hsieh SuperFastHash, с превосходным распределение, лавинное поведение и общее сопротивление столкновения.

вы можете скачать материалы для этого конкурса здесь.

мы узнали, что иногда оптимизация не улучшает результаты на каждом процессоре. Мой вклад был изменен, чтобы хорошо работать на AMD, но выполнялся не очень хорошо на Intel. С другой стороны, произошло то же самое (оптимизация Intel работает неоптимально на AMD).

Итак, как сказал Talljoe: измерьте свои оптимизации, так как они могут нанести ущерб вашей производительности!

в качестве примечания: я не согласен с Ли; Дельфи хороший компилятор и все, но иногда я вижу, что он генерирует код, который просто не является оптимальным (даже при компиляции со всеми оптимизациями). Например, я регулярно вижу его клиринговые регистры, которые уже были очищены только два или три заявления раньше. Или EAX помещается в EBX, только чтобы его сдвинули и вернули в EAX. Что-то в этом роде. Я просто предполагаю здесь, но ручная оптимизация такого рода кода, безусловно, поможет в трудных местах.

прежде всего, хотя; сначала проанализируйте узкое место, затем посмотрите, можно ли использовать лучший алгоритм или структуру данных, затем попробуйте оптимизировать код pascal (например: уменьшить выделение памяти, избежать подсчета ссылок, завершение, try/finally, try/except blocks и т. д.), а затем, только в крайнем случае, оптимизируйте код сборки.


Я написал две "оптимизированные" функции сборки в Delphi или более реализованные известные быстрые хэш-алгоритмы как в fine-tuned Pascal, так и в Borland Assembler. Первой была реализация SuperFastHash, а второй-реализация MurmurHash2, вызванная запросом Томми Прами в моем блоге перевести мою версию c# на реализацию pascal. Это породило обсуждение продолжилось на форумах Embarcadero Обсуждение BASM, что в конце в результате около 20 внедрений (Регистрация последние тесты), который в конечном итоге показал, что было бы трудно выбрать лучшую реализацию из-за больших различий во времени цикла на инструкцию между Intel и AMD.

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


на форуме Delphi / BASM состоялось небольшое обсуждение, которое может вас заинтересовать. Взглянуть на следующее:

http://forums.embarcadero.com/thread.jspa?threadID=13902&tstart=0