Pinned1.hs:
main = print $ sum $ tail ([1..100000000] :: [Int])
然后:
ghc -O2 Pinned1.hs -prof -rtsopts
Pinned1 +RTS -hc -p -xt
hp2ps -e8in -c Pinned1.hp
展示了约40K的固定内存使用,几乎没有堆栈使用,而Pinned1 +RTS -hd -p -xt
则显示这约40K是ARR_WORDS。
Pinned1.prof显示:
total time = 2.14 secs (2137 ticks @ 1000 us, 1 processor)
total alloc = 8,000,046,088 bytes (excludes profiling overheads)
通过查看-sdump-simpl,我可以看到导致这种情况的代码类型。以下是一个稍微复杂一些的例子,从Core反向翻译成Haskell代码,其中发生了同样的事情:
Pinned2.hs:
main = print $ sum $ snd $ wgoC 1 0
wgoC :: Int -> Int -> (Int, [Int])
wgoC n finalState =
let (nxt, ys') = case n of 100000000 -> (finalState, [])
_ -> wgoC (n+1) finalState
in (n, n + nxt * 9: ys')
wgoC将下一个n传递回来,用于计算列表中的值。它报告了约40K的PINNED/ARR_WORDS内存,几乎没有STACK,并输出以下配置文件:
total time = 5.50 secs (5500 ticks @ 1000 us, 1 processor)
total alloc = 16,800,046,112 bytes (excludes profiling overheads)
然而,这个:
Pinned3.hs:
main = print $ sum $ snd $ wgoD 1 0
wgoD :: Int -> Int -> (Int, [Int])
wgoD n finalState =
let (ttl', ys') = case n of 100000000 -> (finalState, [])
_ -> wgoD (n+1) finalState
in (ttl' + n, n + (ttl' + n) * 9 : ys')
在2分钟后没有完成。它只有一个值为1000000时才会完成,我看不到任何固定内存和堆栈使用(约100M)。 (我认为是堆栈使用使其运行速度变慢了。)
我看到Pinned2和Pinned3之间的主要区别在于Pinned3包括来自返回状态的递归调用的信息(返回对的fst:后续值的累积和),但Pinned2仅包括参数wgoC。
所以我的问题是:
Q1)在编译器管道的哪个阶段决定使用固定内存?-ddump-simpl没有明显的区别,也没有-ddump-cmm(虽然有点复杂,所以可能我错过了什么)。
问题2:PINNED/STACK的决策基于什么?(我能找到的关于PINNED的唯一参考资料,比如this,说它对FFI调用很有用,但似乎它也被采用为这种“优化”)。
问题3:是否有一种修改Pinned3的方法,使其使用PINNED?
问题4:(作为最后的手段)是否有其他的Pinned3调整方法,以便有足够的STACK空间,并在合理的时间内运行?(天真地说,我希望与Pinned2具有类似的性能。)
【请注意,我只是想了解PINNED/STACK机制。我相信还有其他的方法可以编写Pinned3,使其融合得很好,几乎不需要任何内存,但这个问题不是关于那个的。】
谢谢!
Pinned3 +RTS -hd -hdARR_WORDS -p -xt -i0.001
来查看它,它显示相同的约40K。这是某种系统/IO开销或类似的东西是有道理的。 - David