Optimization 优化Haskell内环

Optimization 优化Haskell内环,optimization,haskell,Optimization,Haskell,仍在Haskell中进行SHA1实现。我现在有了一个有效的实现,这是内部循环: 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 = iterateB

仍在Haskell中进行SHA1实现。我现在有了一个有效的实现,这是内部循环:

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。除了内联temp变量之外,我想不出任何进一步优化它的方法,但我相信-O2无论如何都会为我做到这一点

有人能看到可以进一步应用的重大优化吗

供参考,k和f呼叫如下。它们是如此简单,我不认为有一种方法可以优化这些其他。除非数据位模块速度慢

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
f::Int->Word32->Word32->Word32->Word32->Word32
f t b c d

|t从ghc-7.2.2产生的岩芯来看,内联效果良好。不太好用的是,在每次迭代中,首先将两个
Word32
值解除绑定以执行工作,然后为下一次迭代重新绑定。拆箱和重新装箱可能会花费惊人的大量时间(和分配)。 您可以使用
Word
而不是
Word32
来避免这种情况。您不能从Data.bit使用
旋转
,但必须自己实现(不难),才能在64位系统上工作。对于
a'
,您必须手动屏蔽高位

另一个看起来不太理想的点是,在每次迭代中,将
t
与19、39和59(如果足够大)进行比较,因此循环体包含四个分支。如果将
iterateBlock'
拆分为四个循环(0-19、20-39、40-59、60-79),并使用常数k1、…、k4和四个函数f1、…、f4(不带
t
参数)来避免分支,并且每个循环的代码大小更小,则可能会更快

而且,正如Thomas所说,使用块数据列表并不是最优的,一个不固定的单词数组/向量可能也会有所帮助

有了爆炸模式,核心看起来好多了。还有两三个不理想的点

                      (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))
看到所有这些吗?它们很便宜,但不是免费的。只需要最外层,通过手工编码步骤和使用
Word
可能会有一些收获

然后比较
t
与19,…,它们出现两次,一次用于确定
k
常数,一次用于
f
变换。单是比较是便宜的,但它们会导致分支,如果没有它们,进一步的内联可能是可能的。我希望这里也能有所收获


不过,名单上还是有。这意味着
w
无法取消装箱,如果
w
可以取消装箱,那么内核可能会更简单。

如果不尝试,我猜很多问题都是将块数据保留在列表中(太多的点/内存流量)。我会努力移动到
Word32
的未绑定向量,并手动展开循环。除此之外,请使用包含
a
b
c
d
e
的严格/解包结构进行尝试;然后你只需要传递一个变量(你一定要在它上面放一个bang模式,对吗?),我也会尝试替换所有的
(另一件事:在C中编写紧凑的算术函数并使用FFI调用通常是一个好主意。如果您注意不引入任何副作用,运行时可以使用对C的快速调用来提供良好的性能。但是C调用仍然需要很多周期,因此请确保您在C-land中做了足够的工作。对于小的事情,最好是冒烟一些
MagicHash
es并使用GHC primops。我在所有函数的所有(!)参数中添加了bang模式(除了
ws
),这使得拆箱工作。很好的发现。你不需要在所有参数上都使用bang模式,但是,当bang在
a,b,c,d,e,a'
上时,所有的东西都是玫瑰色的,k和f是内联的,所有的东西都是可以拆箱的。是的。一般来说,把bang模式放在应该严格的参数上是个好主意。是的。我被GHC拆箱的每件事愚弄了ng立即进入循环体;由于传统上在处理固定宽度类型方面的弱点,我没有进行测试。你确定LLVM不会优化大多数窄化吗?我认为这是可能的。