很难理解Haskell内存分配行为

时间:2011-08-30 17:08:26

标签: performance haskell functional-programming lazy-evaluation josephus

我偶然发现了Haskell和FP,并对可能性感到震惊。我内心的旧数学书呆子可以毫不费力地为实际有用的目的编写天真的代码。尽管如此,我仍然很难理解如何不打出一些令人惊讶的性能瓶颈。

因此,我使用简单的实现编写了非常短的代码片段,然后尝试进行少量更改以查看性能如何响应。 这里有一个我真的无法理解的例子......我写了这个函数,找到了Josephus problem的解决方案,目的,带有一个简单的列表实现。

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

后者运行时间为190毫秒,根据RTS,生产率为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 noob我必须遗漏一些大的东西...这个如何添加参数(预先咀嚼的计算......)如此降低速度?

PS:我知道,如果约瑟夫斯真的和3000名士兵在一起,他们就不需要自杀......

1 个答案:

答案 0 :(得分:9)

第一个解决方案是通过取长度来强制士兵名单的整个脊柱。第二个解决方案仅强制(使用seq)士兵列表的 head 。将left替换为seqlength left之间的{{1}},您将恢复效果。