在检查我正在处理的一些代码的内存使用情况时,我遇到了一个奇怪的问题。
使用function callMethod(id) {
$.ajax({
type: "POST",
url: "delete.php",
data: "id=" + id,
success: function (html) {
alert('Successfully Done!!');
}
});
return false;
}
对一个非常大的列表的元素求和,我得到一个恒定的内存使用。
使用foldl
我也获得了恒定的内存使用量(如预期的那样)。
使用foldl'
内存增长并使我的系统陷入困境(没有像我期望的那样堆栈溢出异常)。
触发它所需的最小代码是:
foldr
其中foldx为
main = print $ foldx (+) 0 [1..100000000000000000000]
,foldl
或foldr
我的印象是(根据Foldr Foldl Foldl'),反之亦然。
我使用上述代码设置了一个repo: https://github.com/framp/hs-fold-perf-test
这里发生了什么?是GHC 8.0.x太聪明了吗? 我在macOS Sierra上
由于
答案 0 :(得分:5)
foldl
和foldl'
在这种情况下,GHC认为foldl
可以严格,并且基本上重写它以利用foldl'
。请参阅下文GHC如何优化foldl
构造。
请注意,这仅适用于您使用优化-O
编译的情况。没有优化,foldl
程序会消耗我所有的记忆和崩溃。
查看ghc -O -fforce-recomp -ddump-simpl foldl.hs
的输出,我们可以看到GHC消除了完全使用的巨大列表,并将表达式优化为尾递归函数:
Rec {
-- RHS size: {terms: 20, types: 5, coercions: 0, joins: 0/0}
Main.main_go [Occ=LoopBreaker] :: Integer -> Integer -> Integer
[GblId, Arity=2, Str=<S,U><S,1*U>]
Main.main_go
= \ (x_a36m :: Integer) (eta_B1 :: Integer) ->
case integer-gmp-1.0.0.1:GHC.Integer.Type.gtInteger#
x_a36m lim_r4Yv
of wild_a36n
{ __DEFAULT ->
case GHC.Prim.tagToEnum# @ Bool wild_a36n of {
False ->
Main.main_go
(integer-gmp-1.0.0.1:GHC.Integer.Type.plusInteger
x_a36m 1)
(integer-gmp-1.0.0.1:GHC.Integer.Type.plusInteger eta_B1 x_a36m);
True -> eta_B1
}
}
end Rec }
这解释了为什么它以恒定的内存使用率运行。
foldr
需要那么多内存? foldr
构建了很多thunk,这些thunk本质上是未完成的计算,最终会保存正确的值。基本上,当尝试评估foldr
表达式时,会发生这种情况:
foldr (+) 0 [1..100]
== (+) 1 $ foldr 0 [2..100]
== (+) 1 $ (+) 2 $ foldr [3..100]
...
== (+) 1 $ (+) 2 $ .. $ (+) 99 $ (+) 100 0 -- at this point there are 100
== (+) 1 $ (+) 2 $ .. $ (+) 99 $ 100 -- unevaluated computations, which
== (+) 1 $ (+) 2 $ .. $ (+) 199 -- take up a lot of memory
...
== (+) 1 $ 5049
== 5050
100000000000000000000
的限制对于thunk占用比你的RAM更多的空间并且你编程崩溃来说非常重要。