仅在 GHC 解释器执行以下操作时在某些情况下发生空间泄漏: concat; !! n

发布于 2024-12-09 05:34:04 字数 976 浏览 4 评论 0原文

我定义了我自己的 concat 版本,myConcat

module Eh where

myConcat []          = []
myConcat ([]:os)     = myConcat os
myConcat ((x:xs):os) = x : myConcat (xs:os)

(!!!)  :: [a] -> Int -> a
xs     !!! n | n < 0 = error "negative index"
[]     !!! _         = error "index too large"
(x:_)  !!! 0         = x
(_:xs) !!! n         = xs !!! (n-1)

如果我执行 myConcat; !!在GHC解释器中,它以300MB/s的速度窃取我的内存,我必须在它召唤OOM杀手之前杀死它。请注意,我将 Eh 加载为“已解释”,在加载之前我不会对其进行编译。

code run in the GHC interpreter        space leak?
myConcat (repeat [1,2,3,4]) !! (10^8)  Yes
concat (repeat [1,2,3,4]) !! (10^8)    No
myConcat (repeat [1,2,3,4]) !!! (10^8) No
concat (repeat [1,2,3,4]) !!! (10^8)   No

现在,如果我编译 Eh (ghc --make -O2 Eh.hs),然后将其加载到解释器中并重新运行这些测试,则不会出现空间泄漏。如果我编译每个测试用例而不是在解释器中运行它们,情况也是如此。

这是怎么回事?


我正在运行 GHC 6.12.3。

I define my own version of concat, myConcat:

module Eh where

myConcat []          = []
myConcat ([]:os)     = myConcat os
myConcat ((x:xs):os) = x : myConcat (xs:os)

(!!!)  :: [a] -> Int -> a
xs     !!! n | n < 0 = error "negative index"
[]     !!! _         = error "index too large"
(x:_)  !!! 0         = x
(_:xs) !!! n         = xs !!! (n-1)

If I do myConcat <some huge list> !! n in the GHC interpreter, it steals my memory at 300MB/s, and I have to kill it before it can summon the OOM killer. Note here that I load Eh as "interpreted", I don't compile it before loading it.

code run in the GHC interpreter        space leak?
myConcat (repeat [1,2,3,4]) !! (10^8)  Yes
concat (repeat [1,2,3,4]) !! (10^8)    No
myConcat (repeat [1,2,3,4]) !!! (10^8) No
concat (repeat [1,2,3,4]) !!! (10^8)   No

Now if I compile Eh (ghc --make -O2 Eh.hs), and then load it in the interpreter and re-run these tests, none of them space leak. Same if I compile each test case instead of running them in the interpreter.

What's going on?


I'm running GHC 6.12.3.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

北城半夏 2024-12-16 05:34:04

这里的问题是严格性。 Haskell 中的计算是不严格的,因此通常仅在确实需要结果时才执行计算。相反,会创建一个所谓的 thunk 来表示尚未执行的计算。

然而,在某些情况下,编译器可以检测到无论如何都需要计算结果,因此用实际计算替换 thunk 的创建。

Haskell Wiki 可能对此有更好的解释。

要修复您的 myConcat 函数,您必须确保它不会通过手动强制严格评估来创建数百万个重击。一种(看起来不太漂亮)的方法可能是:

myConcat []          = []
myConcat ([]:os)     = myConcat os
myConcat ((x:xs):os) = ((:) $! x) myConcat (xs:os)

The issue here is strictness. Evaluation in Haskell is non-strict, so computations are usually performed only if their results are really needed. Instead, a so-called thunk is created that represents the not-yet-performed computation.

In certain cases the compiler can however detect that the result of the computation will be needed anyway and therefore replaces the creation of thunks by the actual computation.

The Haskell Wiki probably explains this better.

To fix your myConcat function you have to make sure it doesn't create millions of thunks by manually forcing strict evaluation. One (not very pretty looking) way of doing this could be:

myConcat []          = []
myConcat ([]:os)     = myConcat os
myConcat ((x:xs):os) = ((:) $! x) myConcat (xs:os)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文