我有一段代码,用sequence
从一个概率分布中重复采样。实际上,它做的事情类似于这样:
sampleMean :: MonadRandom m => Int -> m Float -> m Float
sampleMean n dist = do
xs <- sequence (replicate n dist)
return (sum xs)
除了这个问题有点复杂外,我感兴趣的实际代码是 likelihoodWeighting
函数,位于这个Github repo。
我发现运行时间与 n
的规模不成线性比例关系。特别是一旦 n
超过某个值,就会达到内存限制,并且运行时间急剧增加。我不能确定,但我认为这是因为 sequence
在建立一个长列表的 thunks,这些 thunks 直到调用 sum
之前都没有被求值。
一旦样本数超过约 100,000,程序就会变得非常缓慢。我想优化它(我认为 10,000,000 个样本不应该是问题),所以我决定对其进行分析 - 但我在理解分析器的输出方面遇到了一些困难。
分析器
我创建了一个名为 main.hs
的简短可执行文件,使用 100,000 个样本运行我的函数。这是执行的输出:
$ ghc -O2 -rtsopts main.hs
$ ./main +RTS -s
我注意到的第一件事是 - 它分配了近1.5GB的堆内存,并且将60%的时间用于垃圾回收。这通常是否表明它太懒了?
1,377,538,232 bytes allocated in the heap
1,195,050,032 bytes copied during GC
169,411,368 bytes maximum residency (12 sample(s))
7,360,232 bytes maximum slop
423 MB total memory in use (0 MB lost due to fragmentation)
Generation 0: 2574 collections, 0 parallel, 2.40s, 2.43s elapsed
Generation 1: 12 collections, 0 parallel, 1.07s, 1.28s elapsed
INIT time 0.00s ( 0.00s elapsed)
MUT time 1.92s ( 1.94s elapsed)
GC time 3.47s ( 3.70s elapsed)
RP time 0.00s ( 0.00s elapsed)
PROF time 0.23s ( 0.23s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 5.63s ( 5.87s elapsed)
%GC time 61.8% (63.1% elapsed)
Alloc rate 716,368,278 bytes per MUT second
Productivity 34.2% of total user, 32.7% of total elapsed
以下是结果:
$ ./main +RTS -p
第一次运行时,发现有一个函数被重复调用,我使用了记忆化技术对其进行优化,这使得程序速度提高了2倍。但是,这并没有解决空间泄漏的问题。COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 1 0 0.0 0.0 100.0 100.0
main Main 434 4 0.0 0.0 100.0 100.0
likelihoodWeighting AI.Probability.Bayes 445 1 0.0 0.3 100.0 100.0
distributionLW AI.Probability.Bayes 448 1 0.0 2.6 0.0 2.6
getSampleLW AI.Probability.Bayes 446 100000 20.0 50.4 100.0 97.1
bnProb AI.Probability.Bayes 458 400000 0.0 0.0 0.0 0.0
bnCond AI.Probability.Bayes 457 400000 6.7 0.8 6.7 0.8
bnVals AI.Probability.Bayes 455 400000 20.0 6.3 26.7 7.1
bnParents AI.Probability.Bayes 456 400000 6.7 0.8 6.7 0.8
bnSubRef AI.Probability.Bayes 454 800000 13.3 13.5 13.3 13.5
weightedSample AI.Probability.Bayes 447 100000 26.7 23.9 33.3 25.3
bnProb AI.Probability.Bayes 453 100000 0.0 0.0 0.0 0.0
bnCond AI.Probability.Bayes 452 100000 0.0 0.2 0.0 0.2
bnVals AI.Probability.Bayes 450 100000 0.0 0.3 6.7 0.5
bnParents AI.Probability.Bayes 451 100000 6.7 0.2 6.7 0.2
bnSubRef AI.Probability.Bayes 449 200000 0.0 0.7 0.0 0.7
这是一个堆剖析。我不知道为什么它声称运行时间为1.8秒 - 这次运行大约花费了6秒。
有人可以帮助我解释分析器的输出吗?即识别瓶颈所在,并提供加速的建议?
replicateM n
被定义为sequence . replicate n
。 - Chris Taylorlength xs
总是等于n
,不是吗?所以用n
替换(length xs)
,这样xs
就不必在任何时候完全驻留在内存中了。 - dave4420sum
是一个列表消费者而replicateM
是一个列表生成器。这将消除额外严格性的需要。 - dflemstrrandomR
的调用都使用了相同的生成器,因此它每次生成的“随机”数都相同。您可以尝试类似以下代码(我没有测试过):main = do { a <- sampleMean 10000 (randomRIO (0.0,1.0)); print a }
。 - Chris Taylor