如何在Haskell中提高zipWith的性能

时间:2014-08-12 03:58:58

标签: performance haskell profiling ghc

我已经为Clustering with Data.Clustering.Hierarchical编写了一个代码,但它很慢。

我尝试分析并更改了一些代码,但我不知道为什么zipWith占用了这么多时间? (即使我将列表更改为矢量。)

import Data.Clustering.Hierarchical
import qualified Data.Vector.Primitive as DV
import System.Random
import Control.Monad


main = do
    vectorList <- genTestdata
    let cluster = dendrogram SingleLinkage vectorList getVectorDistance  
    putStrLn $ show cluster

genZero x 
    | x<5 = x
    |otherwise = 0

genVector::IO (DV.Vector Int)
genVector = do
    listRandom <- mapM (\x -> randomRIO (1,30) ) [1..20]
    let intOut = DV.fromList $ map genZero listRandom
    return intOut

genTestdata = do 
    r <- sequence  $ map (\x -> liftM (\y -> (x,y)) genVector) [1..1000]
    return r

getExp2 v1 v2 = d*d
    where
        d = v1 - v2

getExp v1 v2
    | v1 == v2 = 0
    | otherwise = getExp2 v1 v2

tfoldl  d = DV.foldl1' (+) d

changeDataType:: Int -> Double
changeDataType d = fromIntegral d

getVectorDistance::(a,DV.Vector Int)->(a, DV.Vector Int )->Double
getVectorDistance v1 v2 = fromIntegral $ tfoldl dat
    where
        l1 = snd v1
        l2 = snd v2
        dat = DV.zipWith getExp l1 l2

要构建它,请使用:ghc -prof -fprof-auto -rtsopts -O2 log_cluster.hs

使用log_cluster.exe +RTS -p

运行

我的机器上的分析结果如下 - 请注意getVectorDistance.dat的结果:

> log_cluster.exe +RTS -p -RTS

total time  =        8.43 secs   (8433 ticks @ 1000 us, 1 processor)
total alloc = 1,614,252,224 bytes  (excludes profiling overheads)

COST CENTRE            MODULE  %time %alloc
getVectorDistance.dat  Main     49.4   37.8  <------
tfoldl                 Main      5.7    0.0
getExp                 Main      4.5    0.0
getExp2                Main      0.5    1.5

1 个答案:

答案 0 :(得分:2)

在我的评论中提出建议,以下是运行相同代码的时间:

user:~/explorations$ ghc -O2 log_cluster.hs -rtsopts
[1 of 1] Compiling Main             ( log_cluster.hs, log_cluster.o )
Linking log_cluster ...
user:~/explorations$ time ./log_cluster
101000

real    0m0.127s
user    0m0.120s
sys     0m0.000s

以及使用性能分析构建时:

user:~/explorations$ ghc -prof -fprof-auto -O2 log_cluster.hs -rtsopts
[1 of 1] Compiling Main             ( log_cluster.hs, log_cluster.o )
Linking log_cluster ...
user:~/explorations$ time ./log_cluster
101000

real    0m2.937s
user    0m2.920s
sys     0m0.000s

因此,配置文件的构建速度大约慢25倍,这是一个相当大的开销。

此时,我猜你的程序运行缓慢的原因是你正在构建它以进行性能分析。如果非分析构建也太慢,您可能需要使用一些更复杂的分析技术。

当然这有点推测,因为你提供的代码没有编译,所以我不得不填补一些空白。

编辑:要明确,我的立场是添加SCC注释(无论是手动还是自动)限制了ghc可以执行的优化。它们应用得越松散,配置代码和未经编译的代码之间的差异就越大。这可能会导致误导性的配置文件,因为在配置文件代码中显示为瓶颈的功能可能会更少。我想这就是这里发生的事情。

如果分析结果如此扭曲,OP非常合理地询问如何找到瓶颈。我希望在这个例子中,DV.zipWith实际上是一个瓶颈,因为它是唯一能够完成重要工作的函数(参见下面的wrt测试生成代码),但手动检查核心(通过编译-ddump-simpl -ddump-to-file -dsuppress-coercions生成) )表明getVectorDistance产生了一个漂亮的无盒装循环,中间向量完全融合了。我怀疑没有英雄措施可以大大改善。 (见注2)

通常,使用性能分析的最佳方法是从顶部开始并向下钻取。您可以在顶级附近手动添加一些SCC注释,也可以使用-fprof-auto-exported,最好只为几个关键的近顶级模块指定,以获得一个粗略的想法。从那里,您可以进一步向下钻取,方法是向更多模块添加注释,手动添加更多SCC注释,或者,如果您感觉幸运,请切换到-fprof-auto。不幸的是,除非您还添加-fprof-auto-exported语句,否则仅使用module Main (main, getVectorDistance)对此示例没有多大帮助。

另一种方法是使用不同的分析方法。你可以用例如ghc-events-analyze来分析您的代码。这涉及手动添加一些跟踪语句和事后日志的后处理,但它通常会干扰编译器优化。在纯代码中,有时候弄清楚语句的位置以便对它们进行适当的评估是有点棘手的,我的chronograph包可以处理这个(它不支持ghc-events-analyze格式但是我会很快就加上。)

我希望这是完整代码中的一个简化示例。希望其中一种技术能够帮助找到一个可以更容易改进的瓶颈。

注意1:如果数据生成代码与您的完整程序类似,则几乎可以肯定地加快数据生成代码。 System.Random非常慢,使用mwc-randommersenne-random。我对使用DV.fromList略有怀疑,但它可能会融合在一起。

注2:使用-prof -fprof-auto编译时,核心不太好。而不是在两个向量上的未装箱循环,首先创建 new 向量,然后循环遍历该新向量以计算总和。所以你有额外的分配,额外的内存压力和两次遍历而不是一次。这就是为什么配置文件版本明显变慢,以及为什么我认为该配置文件具有误导性:DV.zipWith的时间显着膨胀。