手动执行performGC大幅降内存,为何退出runGhcT后仍存大量垃圾?
我在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
在暂停阶段(即getLine等待输入时),进程内存占用达30GB以上,而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上下文持有大量对象所致。
疑问:为何退出runGhcT后,未手动执行performGC时会残留大量垃圾?
GHC API全局上下文的惰性回收特性
GHC API运行时会维护大量全局状态,比如类型检查器实例、编译器中间表示、模块缓存等。这些对象在runGhcT执行完毕后,已经不再被业务逻辑引用,但GHC的垃圾回收器采用惰性回收策略——只有当内存分配压力达到预设阈值时,才会自动触发回收。而暂停阶段(getLine等待输入)没有新的内存分配操作,垃圾回收器不会主动扫描清理这些闲置对象,导致内存一直被占用。runGhcT内部GC的局限性
在runGhcT内部执行performGC时,GHC的编译上下文仍处于活跃状态:大量编译相关的对象还被当前GhcMonad的上下文持有,垃圾回收器无法将其识别为可回收垃圾。只有当runGhcT完全退出,所有关联的上下文引用被彻底释放后,这些对象才会变成真正的垃圾,但此时若无手动触发,GC不会立即启动。强制求值的范围限制
你在runGhcT内用force强制求值了业务返回值x1,但这仅确保了业务输出对象的完全计算,无法影响GHC API内部生成的大量临时对象——这些对象没有被业务代码引用,但仍占据内存,只有全局GC触发时才会被清理。
内容的提问来源于stack exchange,提问作者Cactus

