Оптимизация внутренних циклов Haskell

Все еще работает над моей реализацией SHA1 в Haskell. Теперь у меня есть рабочая реализация, и это внутренний цикл:

iterateBlock' :: Int -> [Word32] -> Word32 -> Word32 -> Word32 -> Word32 -> Word32 -> [Word32]
iterateBlock' 80 ws a b c d e    = [a, b, c, d, e]
iterateBlock' t (w:ws) a b c d e = iterateBlock' (t+1) ws a' b' c' d' e'
    where
    a' = rotate a 5 + f t b c d + e + w + k t
    b' = a
    c' = rotate b 30
    d' = c
    e' = d

Профилировщик сообщает мне, что эта функция занимает 1/3 от времени выполнения моей реализации. Я не могу думать о том, чтобы не оптимизировать его, кроме как, возможно, встраивать временные переменные, но я верю, что -O2 сделает это для меня в любом случае.

Может ли кто-нибудь увидеть существенную оптимизацию, которая может быть дополнительно применена?

FYI: k и f - ниже. Они такие простые, что я не думаю, что есть способ оптимизировать эти другие. Если модуль Data.Bits не работает?

f :: Int -> Word32 -> Word32 -> Word32 -> Word32
f t b c d
    | t <= 19   = (b .&. c) .|. ((complement b) .&. d)
    | t <= 39   = b `xor` c `xor` d
    | t <= 59   = (b .&. c) .|. (b .&. d) .|. (c .&. d)
    | otherwise = b `xor` c `xor` d

k :: Int -> Word32
k t
    | t <= 19   = 0x5A827999
    | t <= 39   = 0x6ED9EBA1
    | t <= 59   = 0x8F1BBCDC
    | otherwise = 0xCA62C1D6

Ответ 1

Глядя на ядро, созданное ghc-7.2.2, встраивание хорошо работает. Что не так хорошо работает, так это то, что на каждой итерации пара значений Word32 сначала распаковывается, выполняет работу, а затем переустанавливается для следующей итерации. Unboxing и re-boxing могут стоить удивительно большое количество времени (и распределение). Вероятно, вы можете избежать этого, используя Word вместо Word32. Вы не могли бы использовать rotate из Data.Bits, но вам придется реализовать его (не сложно), чтобы он работал и на 64-битных системах. Для a' вам придется вручную маскировать высокие бит.

Еще одна точка, которая выглядит субоптимальной, заключается в том, что в каждой итерации t сравнивается с 19, 39 и 59 (если она достаточно большая), так что тело цикла содержит четыре ветки. Вероятно, это будет быстрее, если вы разделите iterateBlock' на четыре петли (0-19, 20-39, 40-59, 60-79) и используйте константы k1,..., k4 и четыре функции f1,..., f4 (без параметра t), чтобы избежать ветвей и иметь меньший размер кода для каждого цикла.

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

С шаблонами ударов ядро ​​выглядит намного лучше. Остаются две или три менее идеальных точки.

                      (GHC.Prim.narrow32Word#
                         (GHC.Prim.plusWord#
                            (GHC.Prim.narrow32Word#
                               (GHC.Prim.plusWord#
                                  (GHC.Prim.narrow32Word#
                                     (GHC.Prim.plusWord#
                                        (GHC.Prim.narrow32Word#
                                           (GHC.Prim.plusWord#
                                              (GHC.Prim.narrow32Word#
                                                 (GHC.Prim.or#
                                                    (GHC.Prim.uncheckedShiftL# sc2_sEn 5)
                                                    (GHC.Prim.uncheckedShiftRL# sc2_sEn 27)))
                                              y#_aBw))
                                        sc6_sEr))
                                  y#1_XCZ))
                            y#2_XD6))

Посмотрите все эти narrow32Word#? Они дешевые, но не бесплатные. Нужно только самое внешнее, может быть немного урожая путем ручного кодирования шагов и использования Word.

Затем сравнения t с 19,..., они появляются дважды, один раз для определения константы k и один раз для преобразования f. Сравнения сами по себе дешевы, но они вызывают ветки и без них, возможна дальнейшая инкрустация. Я ожидаю, что здесь тоже можно получить немного.

И все же, список. Это означает, что w не может быть распакован, ядро ​​может быть проще, если w были недоступны.