【问题标题】:Can a thunk be duplicated to improve memory performance?可以复制 thunk 以提高内存性能吗?
【发布时间】: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) =&gt; [a]; xs = [1 .. 10000000],则它不太可能被共享。 xs () = [1 .. 10000000] 技巧基本上是一样的(因为它是一个函数绑定,xs 是多态的),但是如果你给函数一个单态签名,这个列表很可能是共享的(GHC 确实如此,至少在优化的情况下)。在这两种情况下,编译器可以共享该列表,因为默认情况下它在两个地方都用于相同的类型,但到目前为止,GHC 还没有进行这种分析。

标签: haskell lazy-evaluation


【解决方案1】:

前段时间我想知道同样的事情,并创建了这样一个 thunk-duplication 函数的原型实现。您可以在我的预印本“dup – Explicit un-sharing in haskell”中了解结果,并在http://darcs.nomeata.de/ghc-dup 处查看代码。不幸的是,这篇论文在今年的 Haskell Symposium 和 Haskell 实施者研讨会上都没有被接受。

据我所知,这个问题没有现成的解决方案;仅作为单元参数技巧的脆弱变通方法可能由于一种或其他编译器优化而中断。

【讨论】:

  • 这正是我想要的。你打算把它放在hackage上吗?
  • 它仍然只是一个原型,并没有在实际应用中得到验证——你读过论文中关于实现限制的部分吗?也许你可以试试存储库中的代码并告诉我它对你的效果如何;如果结果很好,那么我可以上传到hackage。 (使用 ./Setup 比使用 cabal 安装效果更好,所以你应该这样做 darcs get http://darcs.nomeata.de/ghc-dup &amp;&amp; cd ghc-dup &amp;&amp; ghc --make Setup.hs &amp;&amp; ./Setup configure --user &amp;&amp; ./Setup build &amp;&amp; ./Setup install
  • 谢谢。我会试一试并报告。
【解决方案2】:

有趣的问题。我不知道如何实现copyThunk。但是您还可以做一些其他事情(抱歉,如果您已经知道这一点):

xsFunction :: () -> [Int]
xsFunction = const [1..10000000]

better = do
  print $ foldl1' (+) $ xsFunction ()
  print $ length $ xsFunction ()

这里绝对不会将表达式xsFunction () 放入一个thunk 中,它会被计算两次,因此不会造成任何内存膨胀。


对此的一个有趣的跟进是:

  • 可以实现copyThunk吗?
  • haskell 程序员是否应该在这种相对较低级别的优化中搞乱?我们不能假设 ghc 在这方面比我们聪明吗?

【讨论】:

  • 不幸的是,您的方法可能无法按预期工作,因为编译器可能会将[1..1000000] 浮出,因此再次共享它。在我的答案和这张 GHC 票中链接的论文中阅读更多相关信息:hackage.haskell.org/trac/ghc/ticket/917
  • 使用单态类型,优化编译时确实共享列表。
  • 禁用优化会阻止这种情况发生吗?例如,我是否可以简单地编译一个没有优化的库,将表达式移动到这样的函数中,并获得“有效的 thunk 复制”?
  • @MikeIzbicki 如果没有优化,GHC 目前不共享 xs :: () -&gt; [Int]。但是a)你不能依赖剩下的so和b)未优化的代码通常很慢。
【解决方案3】:

xs 变成一个函数。这可能很难看,但很有效,因为它阻止了共享:

let xs () = [1..1000000]

good = do
    print $ foldl1' (+) (xs ())
    print $ length (xs ())

【讨论】:

  • 小心。使用 xs 的单态类型,ghc 在使用优化编译时确实共享列表。
猜你喜欢
  • 2014-02-21
  • 2013-07-10
  • 2011-07-01
  • 2011-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-27
  • 2017-02-05
相关资源
最近更新 更多