我已经为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
答案 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-random或mersenne-random。我对使用DV.fromList
略有怀疑,但它可能会融合在一起。
注2:使用-prof -fprof-auto
编译时,核心不太好。而不是在两个向量上的未装箱循环,首先创建 new 向量,然后循环遍历该新向量以计算总和。所以你有额外的分配,额外的内存压力和两次遍历而不是一次。这就是为什么配置文件版本明显变慢,以及为什么我认为该配置文件具有误导性:DV.zipWith
的时间显着膨胀。