У меня сейчас реализован еще один кандидат SHA3, а именно Grøstl. Это все еще в стадии разработки (очень много), но на данный момент 224-битная версия проходит все KAT. Так что теперь я задаюсь вопросом о производительности (снова: ->). Разница на этот раз в том, что я решил более точно отразить реализацию (оптимизированная) C , то есть я сделал порт из C в Haskell. Оптимизированная версия C использует поиск таблиц для реализации алгоритма. Кроме того, код в значительной степени основан на обновлении массива, содержащего 64-битные слова. Таким образом, я решил использовать изменяемые неупакованные векторы в Haskell.
Мой код Grøstl можно найти здесь: https://github.com/hakoja/SHA3/blob/master/Data/Digest/GroestlMutable.hs
Краткое описание алгоритма: это конструкция Меркле-Дамгарда, с итерацией функции сжатия ( f512M в моем коде) до тех пор, пока остаются 512-битные блоки сообщения. Функция сжатия очень проста: она просто запускает две разные независимые 512-битные перестановки P и Q ( permP и permQ в моем код) и объединяет их вывод. Это их перестановки, которые реализуются с помощью таблиц поиска.
Q1) Первое, что меня беспокоит, это то, что использование изменяемых векторов делает мой код действительно беспорядочным. Я впервые пишу какой-либо основной изменчивый код на Haskell, поэтому я не знаю, как это улучшить. Любые советы о том, как мне лучше структурировать монадический код, будут приветствоваться.
Q2) Второе - производительность. На самом деле это не так уж и плохо, потому что на данный момент код на Haskell медленнее всего в 3 раза. Использование GHC-7.2.1 и компиляция так:
ghc -O2 -Odph -fllvm -optlo-O3 -оптло-петля-уменьшить -оптло-петля-удаление
код Haskell использует 60-е годы. на входе ~ 1 ГБ, в то время как C-версия использует 21-22 с. Но есть некоторые вещи, которые я нахожу странными:
(1) Если я попытаюсь встроить rnd512QM , код займет в 4 раза больше времени, но если я встроу rnd512PM ничего бывает! Почему это происходит? Эти две функции практически идентичны!
(2) Возможно, это сложнее. Я экспериментировал с выполнением двух перестановок параллельно. Но в настоящее время безрезультатно. Это один из примеров того, что я пробовал:
f512 h m = V.force outP `par` (V.force outQ `pseq` (V.zipWith3 xor3 h outP outQ))
where xor3 x1 x2 x3 = x1 `xor` x2 `xor` x3
inP = V.zipWith xor h m
outP = permP inP
outQ = permQ m
При проверке статистики времени выполнения и использовании ThreadScope я заметил, что было создано правильное количество SPARKS, но практически ни одно из них не было фактически преобразовано в полезную параллельную работу. Таким образом, я ничего не получил в ускорении. Мой вопрос становится:
- Функции P и Q слишком малы, чтобы среда выполнения не могла работать параллельно?
- Если нет, неправильно ли я использую пар и pseq (и, возможно, Vector.Unboxed.force)?
- Получу ли я что-нибудь, переключившись на стратегии? И как бы я поступил так?
Большое вам спасибо за ваше время.
EDIT:
Извините, что не предоставил никаких реальных тестов производительности. Тестовый код в репо был предназначен только для себя. Для тех, кто хочет протестировать код, вам нужно скомпилировать main.hs , а затем запустить его как:
. / Main "алгоритм" "testvariant" "выровненный байт"
Например:
. / Main groestl short224 False
или
. / Main groestl e False
( e расшифровывается как «Extreme». Это очень длинное сообщение, предоставляемое с NIST KATS).