如何在Haskell中获得5GB堆的控制权?
目前我正在试验用Snap编写的一个Haskellnetworking服务器,它加载并提供给客户大量的数据。 而且我非常非常难以控制服务器进程。 在随机的时刻,这个过程使用了大量的CPU几分钟到几分钟,并且对客户端请求没有反应。 有时内存使用会在几秒钟内高峰(有时会下降)数百兆字节。
希望有人对长时间运行的Haskell进程有更多的经验,这些进程使用大量的内存,并且可以给我一些指针来使事情更稳定。 我已经debugging了好几天了,现在我开始有点绝望了。
我的设置一点点概述:
-
在服务器启动时,我将大约5千兆字节的数据读入内存中的大(嵌套)Data.Map-like结构。 嵌套地图的值是严格的,地图中的所有值都是数据types,所有的字段也都是严格的。 我花了很多时间确保没有未经评估的thunk被留下。 导入(取决于我的系统负载)大约需要5-30分钟。 奇怪的是连续运行的波动比我想象的要大得多,但这是一个不同的问题。
-
大数据结构位于由Snap服务器产生的所有客户端线程共享的“TVar”内。 客户可以使用小型查询语言来请求数据的任意部分。 数据请求量通常很小(高达300kb左右),只能触及数据结构的一小部分。 所有只读请求都使用“readTVarIO”来完成,所以它们不需要任何STM事务。
-
服务器使用以下标志启动:+ RTS -N -I0 -qg -qb。 这将以multithreading模式启动服务器,禁用空闲时间和并行GC。 这似乎加快了这个过程。
服务器大多运行没有任何问题。 然而,客户端请求每隔一段时间就会超时,CPU高达100%(甚至超过100%),并持续很长一段时间。 同时服务器不再响应请求。
我能想到的几个原因可能会导致CPU使用率:
-
这个请求需要花费很多时间,因为有很多工作要做。 这是不太可能的,因为有时会发生在以前的运行中已经certificate是非常快的请求(我的意思是20-80ms左右)。
-
在数据被处理和发送给客户端之前,仍然有一些未被评估的thunk需要被计算。 这也是不太可能的,与前面的观点一样。
-
不知何故垃圾收集踢,并开始扫描我的整个5GB的堆。 我可以想象这会花费很多时间。
问题是,我不知道如何弄清楚到底发生了什么,该怎么办。 由于导入过程需要很长时间,因此分析结果不会显示任何有用的信息。 似乎没有办法有条件地打开和closures代码内的分析器。
我个人认为GC是这里的问题。 我正在使用GHC7,似乎有很多选项来调整GC的工作方式。
通常非常稳定的数据使用大堆时,您推荐什么样的GC设置?
大量的内存使用和偶尔的CPU峰值几乎肯定是GC踢。你可以看到,如果这确实是这样的,使用RTS选项,如-B
,这会导致GHC蜂鸣声,每当有一个主要的集合,T将告诉你(尤其是查看GC时间是否真的很长)或-Dg
,它打开GC调用的debugging信息(尽pipe需要使用-debug
编译)。
有几件事你可以做,以减轻这个问题:
-
在最初的数据导入中,GHC正在浪费大量的时间来增长堆。 你可以通过指定一个大的
-H
来告诉它一次抓取所有你需要的内存。 -
数据稳定的大堆将被提升到老一代。 如果你用
-G
增加世代数,你可能会得到稳定的数据在最古老的,很lessGC'd一代,而你有更传统的年轻人和老年人之上。 -
根据应用程序其余部分的内存使用情况,可以使用
-F
来调整GHC在再次收集之前会让老一代增长多less。 你可能可以调整这个参数来使这个垃圾收集。 -
如果没有写入,并且你有一个定义良好的接口,那么可能有必要让这个内存不被GHCpipe理(使用C FFI),这样就没有超级GC的机会。
这些都是猜测,所以请与您的特定应用程序进行testing。
我有一个非常类似的嵌套地图堆1.5GB的问题。 默认情况下,闲置的GC会在每个GC上得到3-4秒的冻结,而在闲置的GCclosures的情况下(+ RTS -I0),在几百个查询后,我会等待17秒,导致客户端时间退房手续。
我的“解决scheme”首先是增加了客户端的超时时间,并要求人们容忍这一点,而98%的查询时间大约是500毫秒,大约2%的查询速度会很慢。 但是,想要一个更好的解决scheme,我最终运行了两台负载平衡的服务器,每隔200个查询执行一次,从集群中脱机执行performGC,然后再执行。
侮辱受伤,这是一个原始的Python程序的重写,从来没有这样的问题。 公平地说,我们确实获得了大约40%的性能增长,死锁并行和更稳定的代码库。 但是这个讨厌的GC问题…