可变,(可能是并行)Haskell代码和性能调优

时间:2011-11-16 17:24:24

标签: performance haskell parallel-processing mutable

我现在有implemented another SHA3候选人,即Grøstl。这仍然在进行中(非常如此),但目前224位版本通过了所有KAT。所以现在我想知道性能(再次: - >)。这次的不同之处在于,我选择更接近镜像(optimized) C implementation,即我从C到Haskell建立了一个端口。优化的C版本使用表查找来实现该算法。此外,代码主要基于更新包含64位字的数组。因此,我选择在Haskell中使用可变的无盒载体。

我的Grøstl代码可以在这里找到:https://github.com/hakoja/SHA3/blob/master/Data/Digest/GroestlMutable.hs

算法的简短描述:这是一个Merkle-Damgård构造,只要剩下512位的消息块,就会迭代压缩函数(在我的代码中 f512M )。压缩功能非常简单:它只运行两个不同的独立512位排列 P Q permP permQ 在我的代码中)并结合他们的输出。它的这些排列是由查找表实现的。

Q1)困扰我的第一件事是使用可变向量使我的代码看起来非常难看。这是我第一次在Haskell中编写任何主要的可变代码,所以我真的不知道如何改进它。关于如何更好地构建monadic代码的任何提示都会受到欢迎。

Q2)第二是表现。实际上它并不太糟糕,因为目前Haskell代码只慢了3倍。使用GHC-7.2.1并进行编译:

  

ghc -O2 -Odph -fllvm -optlo-O3 -optlo-loop-reduce -optlo-loop-deletion

Haskell代码使用60秒。输入约为1GB,而C版本使用21-22s。但有一些我觉得奇怪的事情:

(1)如果我尝试内联 rnd512QM ,则代码需要4倍的时间,但如果我内联 rnd512PM < / strong>没有任何反应!为什么会这样?这两个功能几乎相同!

(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,但几乎没有实际转换为有用的并行工作。因此,我在加速方面一无所获。我的问题就变成了:

  1. P和Q函数是否太小而运行时无法并行运行?
  2. 如果没有,我是否使用 par pseq (可能还有Vector.Unboxed.force)错?
  3. 转换到策略会获得任何收益吗?那我该怎么做呢?
  4. 非常感谢您的时间。

    修改

    很抱歉没有提供任何真正的基准测试。回购中的测试代码仅供我自己使用。对于那些想要测试代码的人,你需要编译main.hs,然后运行它:

      

    ./ main“algorithm”“testvariant”“byte aligned”

    例如:

      

    ./ main groestl short224 False

      

    ./ main groestl e False

    e 代表“Extreme”。这是NIST KATS提供的非常长的消息)。

2 个答案:

答案 0 :(得分:3)

我检查了回购,但是没有简单的基准来运行和玩,所以我的想法只是来自眼球的代码。编号与您的问题无关。

1)我很确定force没有做你想做的事 - 它实际上强制了底层矢量的副本。

2)我认为使用unsafeThaw和unsafeFreeze有点奇怪。我只是将f512M放入ST monad并完成它。然后运行它:

otherwise = \msg -> truncate G224 . outputTransformation . runST $ foldM f512M h0_224 (parseMessage dataBitLen 512 msg)

3)V.foldM'有点傻 - 你可以在列表上使用正常(严格)foldM - 在第二个参数中折叠向量似乎不会买任何东西。

4)我对columnM和unsafeReads中的刘海表示怀疑。

也...

a)我怀疑xoring未装箱的载体可能在低于zipWith的水平上实现,使用Data.Vector内部。

b)但是,最好不要这样做,因为它可能会干扰矢量融合。

c)在检查时,extractByte看起来效率不高?而不是使用fromIntegral截断,可以使用modquot,然后使用单个fromIntegral直接转到Int。

答案 1 :(得分:1)

  1. 请务必使用-threaded -rtsopts进行编译,然后使用+RTS -N2执行。没有它,您将不会有多个OS线程来执行计算。

  2. 尝试引发其他地方引用的计算,否则可能会收集这些计算:

  3. _

    f512 h m = outP `par` (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 = V.force $ permP inP
             outQ = V.force $ permQ m
    

    _

    3)如果你进行了切换,那么parseBlock接受严格的字节串(或者在需要时接受块和包的延迟),那么你可以使用Data.Vector.Storable并且可能避免一些复制。