GHC的垃圾收集RTS选项

Dan*_*kov 38 performance garbage-collection haskell ghc

我有一个Haskell程序,它处理一个文本文件并构建一个Map(有几百万个元素).整件事可以运行2-3分钟.我发现调整-H和-A选项会对运行时间产生很大影响.

有关于RTS的这个功能的文档,但是对我来说这是一个很难读的,因为我不知道GC理论的算法和术语.我正在寻找一个技术性较低的解释,最好是针对Haskell/GHC.是否有关于为这些选项选择合理值的参考?

编辑:这是代码,它为给定的单词列表构建一个trie.

buildTrie :: [B.ByteString] -> MyDFA 
buildTrie l = fst3 $ foldl' step (emptyDFA, B.empty, 1) $ sort $ map B.reverse l where
    step :: (MyDFA , B.ByteString, Int) -> B.ByteString -> (MyDFA , B.ByteString, Int)
    step (dfa, lastWord, newIndex) newWord = (insertNewStates, newWord, newIndex + B.length newSuffix) where            
        (pref, lastSuffix, newSuffix) = splitPrefix lastWord newWord
        branchPoint = transStar dfa pref

        --new state labels for the newSuffix path
        newStates = [newIndex .. newIndex + B.length newSuffix - 1]
        --insert newStates
        insertNewStates = (foldl' (flip insertTransition) dfa $ zip3 (branchPoint:init newStates) (B.unpack newSuffix) newStates)
Run Code Online (Sandbox Code Playgroud)

Sim*_*low 68

一般来说,垃圾收集是一种空间/时间权衡.为GC提供更多空间,所需时间更短.还有(许多)其他因素在起作用,特别是缓存,但空间/时间权衡是最重要的因素.

权衡如下:程序分配内存直到达到某个限制(由GC的自动调整参数决定,或通过RTS选项明确决定).达到限制时,GC会跟踪程序当前使用的所有数据,并回收不再需要的数据使用的所有内存.延迟此过程的时间越长,同时数据就越不可达("死"),因此GC会避免跟踪该数据.延迟GC的唯一方法是使更多内存可用于分配; 因此,更多的内存等于更少的GC,等于更低的GC开销.粗略地说,GHC的-H选项允许您设置GC使用的内存量的下限,因此可以降低GC开销.

GHC使用分代GC,这是对基本方案的优化,其中堆被分成两代或更多代.对象被分配到"年轻"一代,而那些活得足够长的对象被提升为"老一代"(在2代设置中).年轻一代比老一代更频繁地收集,其想法是"大多数物品都很年轻",所以年轻一代的收藏很便宜(他们没有追踪太多数据),但他们回收了大量的记忆.粗略地说,-A选项设置年轻代的大小 - 即在收集年轻代之前将分配的内存量.

-A的默认值为512k:最好让年轻一代保持小于L2缓存,如果超过L2缓存大小,性能通常会下降.但是,相反的方向是GC空间/时间权衡:使用非常大的年轻代可能会通过减少GC必须完成的工作量来超过缓存优势.这并不总是发生,它取决于应用程序的动态,这使GC很难自动调整自身.-H选项还会增加年轻代的大小,因此也会对缓存使用产生负面影响.

底线是:玩弄选项,看看哪些有效.如果你有足够的内存,你很可能通过使用-A或-H来提高性能,但不一定如此.

  • @djv我今天写了这样一个工具:http://hackage.haskell.org/package/ghc-gc-tune (15认同)
  • 不幸的是,它并不那么简单.如果你的程序需要大量的内存,那么更有可能使用更大的-A或-H将有所帮助,但并非总是如此 - 最好的办法是尝试并查看(使用+ RTS -s来测量).人们看到的最常见的性能问题是当程序在很短的时间内创建大量长期存在的数据时(就像你的程序一样).在这种情况下,"大多数物体死亡年轻"的世代GC假设是无效的,而代际GC正在伤害而不是帮助.这是使用大-A值通常有帮助的地方. (8认同)
  • 顺便说一句,拥有一个搜索可能选项空间的工具并为您的程序提供最佳选项并不是很有用.我想标准技术应该可行,因为功能足够顺畅. (3认同)
  • 谢谢你的回答.所以,如果我理解正确,如果程序创建大数据结构并使用大量内存,最好提前给它一个提示并设置大-H和-A.这就是我有限的经验所表明的.是对的吗?还有一些看起来更神奇的选项:-c,-F,-G.他们能像-H一样改善运行时间吗?(我发现他们可以让事情变得更糟) (2认同)

cla*_*aus 8

对于较小的数据集,可能会重现该问题,从而更容易看到正在发生的事情.特别是,我建议熟悉分析:

然后,检查您看到的内存配置文件是否符合您的期望(您无需了解所有分析选项以获取有用的图形).严格foldl'与非严格元组作为累加器的组合将是我要看的第一件事:如果元组的组件没有被强制,那么递归正在构建未评估的表达式.

顺便说一句,你可以通过手工评估你的代码来获得真正微小的数据集,从而建立一个关于这些事情的良好直觉.一些迭代就足以看出表达式是否以适合您的应用程序的方式得到评估或保持不被评估.

  • 不要忘记只需使用+ RTS -h运行程序即可获得快速堆配置文件,无需重新编译以进行性能分析.这会告诉您堆配置文件的形状以及占用空间的内容,但不会告诉您程序的哪个部分创建了数据,因为您需要重新编译以进行性能分析. (2认同)