手动执行GC显著降低内存占用。

3

我的程序在 IO 中使用 GHC API,在 GhcMonad 内部执行一些计算,并在返回结果之前强制执行结果;大致像这样:

main :: IO ()
main = do
    x <- runGhcT $ do
        x0 <- someGhcFunctionality
        x1 <- furtherProcessing
        liftIO . evaluate . force $ x1

    putStrLn "Done with GHC."
    _ <- getLine

    continueProcessingOutsideGhc x

在暂停点,我可以看到该进程使用了30GB+的RAM;由于continueProcessingOutsideGhc也会占用一定量的内存,因此这可能导致在continueProcessingOutsideGhc中途内存不足。

然而,我发现在暂停点手动强制进行垃圾回收可以显著改变情况:

import System.Mem

main :: IO ()
main = do
    x <- runGhcT $ do
        x0 <- someGhcFunctionality
        x1 <- furtherProcessing
        liftIO . evaluate . force $ x1

    putStrLn "Done with GHC."
    _ <- getLine

    performGC 
    putStrLn "Done with performGC."
    _ <- getLine

    continueProcessingOutsideGhc x

那个 `performGC` 行将内存占用减少了85%,约为4GB。这当然足够让 `continueProcessingOutsideGhc` 完成任务。应该注意的是,在 `runGhcT` 中执行 `liftIO performGC` 没有同样的效果;我猜这很有道理,如果全局GHC上下文保留了很多东西。
我想要理解的是,为什么在没有手动执行 `performGC` 的情况下退出 `runGhcT` 后所有的垃圾都会留在那里。

2
你写道:“这可能会导致在 continueProcessingOutsideGhc 中内存不足。”你有观察到这种情况发生了吗,还是你只是担心它可能会发生?我和Daniel Wagner一样怀疑:GC应该根据需要运行,因此无论你是否进行GC,continueProcessingOutsideGhc都应该成功。如果你可以证明 performGC 实际上可靠地影响是否会出现内存不足,那将是一个有趣的错误跟踪(对于比我更有专业知识的人)。 - amalloy
1
@amalloy,是的,实际上在continueProcessingOutsideGhc期间出现内存不足的情况确实经常发生。这就是我首先进行performGC操作的原因。 - Cactus
1个回答

1

我认为这并不复杂。垃圾收集器会在运行时注意到需要进行垃圾回收时运行--通常是当"短暂"代填满时。因此,如果您最近生成了大量垃圾,它将存在一段时间,直到您分配了一堆新的东西并触发了垃圾回收。

runGhcT结束以来,您没有分配任何内存(也许有一些,但肯定不多),因此您没有触发垃圾回收。


1
但是在实际耗尽内存之前,GC不应该至少被触发吗? GHC分配器在这个领域如何与操作系统交互?我希望如果GHC请求更多内存而操作系统拒绝,则GHC会尝试通过GC释放一些空间。 - Cactus
1
我刚试了一下,发现如果我告诉 GHC 最多使用 25G 的 RAM(使用 -M25G RTS 选项),那么 GhcMonad 部分只使用了 20G 的内存,整个程序成功运行而无需手动调用 performGC。所以 GHC 愿意回收一些垃圾,但当“仅”操作系统内存不足时,它似乎并不感到有必要?! - Cactus
嗯……我第一次看问题时以为 continueProcessingOutsideGhc 的意思是完全在 Haskell 以外,比如通过 FFI。这样的话显然 Haskell 运行时不能指望自己修复,因为它甚至没有控制权。你的意思是即使在 runGhcT 之后执行纯 Haskell 代码仍然会出现 OOM ?如果是这样,那么我同意这更加令人困惑。 - Daniel Wagner
1
既然你提到了,continueProcessingOutsideGhc 函数确实涉及到一些 FFI。但是整个函数并不是一个单独的 FFI 调用或类似的东西——它包含了一些复杂的 Haskell 代码,其中一部分是纯函数,一部分在 IO 中,还有一些涉及到 FFI。 - Cactus
@Cactus 如果您将FFI调用存根化,问题是否仍然存在? - Daniel Wagner
1
那将是相当复杂的测试。 - Cactus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接