Optimization 优化Haskell内环
仍在Haskell中进行SHA1实现。我现在有了一个有效的实现,这是内部循环: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
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不会优化大多数窄化吗?我认为这是可能的。