Наиболее эффективная функция хеширования Unicode для Delphi 2009 - PullRequest
9 голосов
/ 17 июня 2009

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

Я изначально начал с Gabr 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. Я заметил, что процедуры Габра не были обновлены до Delphi 2009.

Затем я обнаружил HashNameMBCS в SysUtils Delphi 2009 и преобразовал его в эту простую функцию (где «string» - это строка Unicode 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 }

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

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 $00401348
0048DEB1 85C0             test eax,eax
0048DEB3 7E1C             jle $0048ded1
0048DEB5 BA01000000       mov edx,$00000001
Process.pas.1651: Result := (Result shl 5) or (Result shr 27);
0048DEBA 8BCB             mov ecx,ebx
0048DEBC C1E105           shl ecx,$05
0048DEBF C1EB1B           shr ebx,$1b
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-$02]
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 $0048deba
Process.pas.1654: end; { HashOf }
0048DED1 8BC3             mov eax,ebx

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

Скорость имеет значение. Могу ли я что-нибудь сделать для улучшения написанного мной кода на Паскале или сгенерированного моим кодом ассемблера?


Followup.

Я наконец-то выбрал функцию HashOf, основанную на SysUtils.HashNameMBCS. Похоже, он дает хорошее распределение хеша для строк Unicode и выглядит довольно быстро.

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

Ответы [ 4 ]

9 голосов
/ 17 июня 2009

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

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

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

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

Обратите внимание, что на разных машинах код будет выполняться по-разному, поэтому, если скорость ДЕЙСТВИТЕЛЬНО важна, сравните ее с оборудованием, на котором планируется запустить конечное приложение. Я готов поспорить, что разница в мегабайтах данных будет считаться миллисекундами, что гораздо меньше, чем у вас отнимает операционная система.


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

5 голосов
/ 17 июля 2009

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

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

5 голосов
/ 22 июня 2009

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

Это отмечено как исключительно быстро, часто в два-четыре раза быстрее чем сопоставимые алгоритмы, такие как FNV, Дженкинс lookup3 и Hsieh's SuperFastHash, с отличным распределение, лавинное поведение и общее сопротивление столкновению.

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

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

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

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

Прежде всего, хотя; Сначала проанализируйте свое узкое место, затем посмотрите, можно ли использовать лучший алгоритм или структуру данных, затем попытайтесь оптимизировать код на языке Паскаль (например: уменьшить объем памяти, избежать подсчета ссылок, финализации, попробовать / наконец, попробовать / исключить блоки и т.д.) и затем, только в качестве последнего средства, оптимизируйте код сборки.

4 голосов
/ 17 июня 2009

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

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

...