【发布时间】:2012-07-26 18:34:12
【问题描述】:
我在 Haskell 中与惰性求值的斗争之一是难以推理内存使用情况。我认为复制 thunk 的能力会让我更容易做到这一点。这是一个例子。
让我们创建一个非常大的列表:
let xs = [1..10000000]
现在,让我们创建一个坏函数:
bad = do
print $ foldl1' (+) xs
print $ length xs
如果没有优化,这会占用几十 MB 的内存。垃圾收集器无法在折叠期间释放 xs,因为稍后计算长度时需要它。
是否可以像这样重新实现这个函数:
good = do
(xs1,xs2) <- copyThunk xs
print $ foldl1' (+) xs1
print $ length xs2
现在,xs1 和 xs2 将表示相同的值,但在内存中也是相互独立的,因此垃圾收集器可以在折叠期间释放内存,从而防止内存浪费。 (我认为这会稍微增加计算成本?)
显然在这个简单的例子中,重构代码可以很容易地解决这个问题,但是如何重构似乎并不总是很明显。或者有时重构会大大降低代码的清晰度。
【问题讨论】:
-
如果您将列表设为多态
let xs :: (Num a, Enum a) => [a]; xs = [1 .. 10000000],则它不太可能被共享。xs () = [1 .. 10000000]技巧基本上是一样的(因为它是一个函数绑定,xs是多态的),但是如果你给函数一个单态签名,这个列表很可能是共享的(GHC 确实如此,至少在优化的情况下)。在这两种情况下,编译器可以共享该列表,因为默认情况下它在两个地方都用于相同的类型,但到目前为止,GHC 还没有进行这种分析。