Я работаю над реализацией одного из кандидатов SHA3, JH. Я нахожусь в точке, где алгоритм передает все KAT (известные тесты ответа), предоставленные NIST, и также сделал его экземпляром Crypto-API. Таким образом, я начал изучать его работу. Но я довольно новичок в Haskell и не знаю, что искать при профилировании.
В настоящий момент мой код последовательно медленнее, чем эталонная реализация, написанная на C, в 10 раз для всех входных длин (код C, найденный здесь: http://www3.ntu.edu.sg/home/wuhj/research/jh/jh_bitslice_ref64.h).
Мой код Haskell находится здесь: https://github.com/hakoja/SHA3/blob/master/Data/Digest/JHInternal.hs.
Теперь я не ожидаю, что вы будете пробираться по всему моему коду, а я просто хочу получить несколько советов по нескольким функциям. Я выполнил некоторые тесты производительности, и это (часть) файла производительности, созданного GHC:
Tue Oct 25 19:01 2011 Time and Allocation Profiling Report (Final)
main +RTS -sstderr -p -hc -RTS jh e False
total time = 6.56 secs (328 ticks @ 20 ms)
total alloc = 4,086,951,472 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc
roundFunction Data.Digest.JHInternal 28.4 37.4
word128Shift Data.BigWord.Word128 14.9 19.7
blockMap Data.Digest.JHInternal 11.9 12.9
getBytes Data.Serialize.Get 6.7 2.4
unGet Data.Serialize.Get 5.5 1.3
sbox Data.Digest.JHInternal 4.0 7.4
getWord64be Data.Serialize.Get 3.7 1.6
e8 Data.Digest.JHInternal 3.7 0.0
swap4 Data.Digest.JHInternal 3.0 0.7
swap16 Data.Digest.JHInternal 3.0 0.7
swap8 Data.Digest.JHInternal 1.8 0.7
swap32 Data.Digest.JHInternal 1.8 0.7
parseBlock Data.Digest.JHInternal 1.8 1.2
swap2 Data.Digest.JHInternal 1.5 0.7
swap1 Data.Digest.JHInternal 1.5 0.7
linearTransform Data.Digest.JHInternal 1.5 8.6
shiftl_w64 Data.Serialize.Get 1.2 1.1
Detailed breakdown omitted ...
Теперь быстро о алгоритме JH:
Это хэш-алгоритм, который состоит из функции сжатия F8, которая повторяется до тех пор, пока существуют входные блоки (длиной 512 бит). Именно так работают SHA-функции. Функция F8 состоит из функции E8, которая применяет круглую функцию 42 раза. Сама круглая функция состоит из трех частей: sbox, линейное преобразование и перестановка (называемый swap в моем коде).
Таким образом, разумно, что большую часть времени тратится на круглую функцию. Тем не менее я хотел бы знать, как можно улучшить эти части. Например: функция blockMap - это просто функция утилиты, отображающая функцию над элементами в 4-кортеже. Так почему это так плохо? Любые предложения приветствуются, а не только по отдельным функциям, т.е. Существуют ли структурные изменения, которые вы сделали бы для повышения производительности?
Я попытался взглянуть на вывод Core, но, к сожалению, так на моей голове.
Я присоединяю некоторые из профилей кучи в конце, а также в случае, если это может быть интересно.
EDIT:
Я забыл упомянуть о моей установке и создании. Я запускаю его на машине x86_64 Arch Linux, GHC 7.0.3-2 (я думаю), с параметрами компиляции:
ghc --make -O2 -funbox-strict-fields
К сожалению, при компиляции через C или LLVM, похоже, есть ошибка на платформе Linux, что дает мне ошибку:
Ошибка: выражение .size для XXXX не оценивает константу
поэтому я не смог увидеть эффект этого.