很难理解 Haskell 内存分配行为
我偶然发现了 Haskell 和 FP,并对其中的可能性感到震惊。我内心的老数学书呆子可以毫不费力地为实际有用的目的编写简单的代码。然而,尽管进行了所有阅读,我仍然很难理解如何不遇到一些令人惊讶的性能瓶颈。
因此,我使用简单的实现编写了非常短的代码,然后尝试进行一些小的更改以查看性能如何响应。 这是一个我真的无法理解的例子...我编写了这个函数,它找到了 约瑟夫斯问题,故意使用简单的列表实现。
m = 3
n = 3000
main = putStr $ "Soldier #" ++ (show $ whosLeft [1..n]) ++ " survived...\n"
whosLeft [lucky] = lucky
whosLeft soldiers = whosLeft $ take (length soldiers -1) $ drop m $ cycle soldiers
根据 RTS,后者的运行时间为 190 毫秒,生产率为 63%。
然后我想尝试的第一件事是删除(长度士兵-1)并将其替换为递减整数。
运行时间跃升至 900 毫秒,生产率下降至 16%,并且使用的内存比上面更简单的代码多 47 倍!所以我添加了严格的评估,强制使用 Int 类型,尝试了删除全局变量等,但没有多大效果。我就是无法理解这种放缓。
m = 3::Int
n = 3000::Int
main = putStr $ "Soldier #" ++ (show $ whosLeft n [1..n]) ++ " survived...\n"
whosLeft 1 [lucky] = lucky
whosLeft n' soldiers = n' `seq` left `seq` whosLeft (n'-1) left
where left = take (n'-1) $ drop m $ cycle soldiers
我已经筛选了与性能相关的文章和帖子,但我似乎没有得到任何关于这一点的提示。作为一个 Haskell 菜鸟,我一定错过了一些大东西...这个添加的参数(预先咀嚼的计算...)怎么会降低速度这么多?
PS:我知道,如果约瑟夫斯真的和三千士兵在一起,他们就不需要自杀了……
I stumbled upon Haskell and FP and got stunned by the possibilities. And the old maths nerd inside me had no trouble writing naive code for actual useful purposes. However inspite of all the reading I still really have a hard time understanding how not to hit some surprising performance bottlenecks.
So I write very short pieces of code with naive implementations and then try little changes to see how the performance responds.
And here's one example I really can't get around to understand... I wrote this function that finds a solution to Josephus problem, on purpose with a naive list implementation.
m = 3
n = 3000
main = putStr $ "Soldier #" ++ (show $ whosLeft [1..n]) ++ " survived...\n"
whosLeft [lucky] = lucky
whosLeft soldiers = whosLeft $ take (length soldiers -1) $ drop m $ cycle soldiers
The latter runs in 190 ms, with a productivity of 63% according to the RTS.
Then the first thing I wanted to try was to remove the (length soldier -1) and replace it with a decrementing integer.
The running time leaped up to 900 ms and productivity down to 16%, and uses 47 times more memory than the simpler code above ! So I added strict evaluation, forced the Int type, tried things like removing the global variables and others, yet not to much avail. And I just can't understand this slowdown.
m = 3::Int
n = 3000::Int
main = putStr $ "Soldier #" ++ (show $ whosLeft n [1..n]) ++ " survived...\n"
whosLeft 1 [lucky] = lucky
whosLeft n' soldiers = n' `seq` left `seq` whosLeft (n'-1) left
where left = take (n'-1) $ drop m $ cycle soldiers
I have sieved through performance related articles and posts, but I just don't seem to get a hint about this. Still being a Haskell noob I must be missing something big... How can this one added parameter (pre-chewed computation...) reduce the speed so much ?
PS : I know, if Josephus really had been with 3000 soldiers, they wouldn't have needed to suicide...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
第一个解决方案通过获取士兵列表的长度来强制其整个脊柱。第二种解决方案仅强制(使用
seq
)士兵列表的head。将seq
之间的left
替换为length left
,您将恢复性能。The first solution forces the whole spine of the soldiers list by taking its length. The second solution only forces (using
seq
) the head of the soldiers list. Replace theleft
in-between theseq
s with alength left
and you'll get your performance back.