目前我正在尝试使用Snap编写的一个小Haskell Web服务器来加载并向客户端提供大量数据。而且我非常非常难以控制服务器进程。在随机的瞬间,该过程使用大量的CPU几秒到几分钟,并且对客户端请求没有反应。有时内存使用量会在几秒钟内达到峰值(有时会下降)数百兆。
希望有人对使用大量内存的长时间运行的Haskell进程有更多的经验,并且可以给我一些指导以使事情更稳定。我已经调试了几天了,我开始有点绝望了。
我的设置概述:
在服务器启动时,我将大约5千兆字节的数据读入内存中的大(嵌套)Data.Map相似结构。嵌套映射是值严格的,并且映射中的所有值都是数据类型,其所有字段也都是严格的。我花了很多时间确保没有留下未评估的thunk。导入(取决于我的系统负载)大约需要5-30分钟。奇怪的是连续运行的波动比我预期的要大,但这是一个不同的问题。
大数据结构存在于由“Snap”服务器生成的所有客户端线程共享的“TVar”中。客户端可以使用小型查询语言请求数据的任意部分。数据请求的数量通常很小(高达300kb左右),只接触数据结构的一小部分。所有只读请求都使用'readTVarIO'完成,因此它们不需要任何STM事务。
使用以下标志启动服务器:+ RTS -N -I0 -qg -qb。这将以多线程模式启动服务器,禁用空闲时间和并行GC。这似乎加快了这个过程。
服务器大多运行没有任何问题。但是,偶尔客户端请求会超时并且CPU达到100%(甚至超过100%),并且会持续这么长时间。同时服务器不再响应请求。
我可以想到几个可能导致CPU使用的原因:
请求需要花费很多时间,因为还有很多工作要做。这有点不太可能,因为有时它会发生在之前运行中证明速度非常快的请求中(快速,我的意思是20-80ms左右)。
在处理数据并将数据发送到客户端之前,仍然需要计算一些未评估的thunk。这也不太可能,原因与上一点相同。
以某种方式垃圾收集开始并开始扫描我的整个5GB堆。我可以想象这会花费很多时间。
问题在于我不知道如何弄清楚到底发生了什么以及如何应对。因为导入过程花了这么长时间的分析结果,所以没有向我展示任何有用的东西。似乎没有办法从代码中有条件地打开和关闭探查器。
我个人怀疑GC是问题所在。我正在使用GHC7,它似乎有很多选项可以调整GC的工作原理。
在使用通常非常稳定的数据的大堆时,您建议使用哪些GC设置?
答案 0 :(得分:29)
大量的内存使用和偶尔的CPU峰值几乎可以肯定是GC的开始。你可以通过使用像-B
这样的RTS选项来确定是否确实如此,这会导致GHC在有主要集合时发出蜂鸣声, -t
会在事后告诉您统计信息(特别是,查看GC时间是否真的很长)或-Dg
,它会打开GC调用的调试信息(尽管您需要使用{{进行编译) 1}})。
您可以采取以下措施来缓解此问题:
在初始导入数据时,GHC正在浪费大量时间来增加堆。您可以通过指定较大的-debug
来告诉它一次性获取所需的所有内存。
具有稳定数据的大型堆将被提升为旧一代。如果使用-H
增加代数,您可以将稳定数据放在最古老的,非常少的GC代中,而您可以使用更传统的年轻和旧的数据集。 / p>
根据应用程序其余部分的内存使用情况,您可以使用-G
来调整GHC在再次收集之前让旧代增长多少。您可以调整此参数以使其无垃圾收集。
如果没有写入,并且您有一个定义良好的接口,那么可能值得使GHC无法管理此内存(使用C FFI),这样就不会有超级GC如初。
这些都是猜测,所以请使用您的特定应用进行测试。
答案 1 :(得分:2)
我有一个非常类似的问题,1.5GB的嵌套地图堆。默认情况下,当空闲GC打开时,我会在每个GC上冻结3-4秒,并且关闭空闲GC(+ RTS -I0),在几百次查询后我会得到17秒的冻结,从而导致客户端时间-out。
我的“解决方案”首先是增加客户端超时并要求人们容忍这一点,而98%的查询大约是500毫秒,大约2%的查询会死得很慢。但是,想要一个更好的解决方案,我最终运行了两个负载均衡的服务器,并且每隔200个查询就从集群中脱机执行performGC,然后重新开始运行。
添加侮辱伤害,这是一个原始的Python程序的重写,从来没有这样的问题。公平地说,我们确实获得了大约40%的性能提升,简单的并行化和更稳定的代码库。但这个讨厌的GC问题......