Scala并发性能下降

4
我要先说明一下,我相对于Java/Scala来说还是个新手,所以不能排除有些明显的问题我可能没有注意到。
我有一个Scala应用程序,它通过Hibernate连接到MySQL数据库。该应用程序旨在处理大量数据,约为2,750,000条记录,因此我尽可能地进行了优化。
它运行在我的工作站上,这是一台四核Intel Xeon处理器,配备6GB RAM(1033Mhz),并且在前70k条记录中运行得很好和迅速,完成时间约为15分钟。但到了90k时,它已经花费了约25分钟,变得非常缓慢。
我已经检查了Hibernate代码的计时器,并且发现数据库检索所花费的时间与通常相同。我甚至尝试强制手动垃圾回收,但也不起作用。
相关代码如下:
val recordCount = repo.recordCount
val batchSize = 100
val batches = (0 to recordCount by batchSize).toList
val batchJobs = {
    for (batchStart <- batches) yield {
        future(new RecordFormatter().formatRecords(new Repo(sessionFactory.openSession),batchStart,batchSize)
    }
awaitAll(100000,batchJobs: *_)

在RecordFormatter中(实际上它的命名并不是这样,以防你对我的命名方案感到疯狂),它查询接下来的100个记录,然后再使用开始和结束值之间的between查询来检索实际记录,然后将其作为CSV输出到文本文件中。根据计时器输出,记录格式化程序中的每个操作需要约5秒钟来检索记录,然后需要0.1秒钟将其输出到文件。

尽管如此,一旦减速,它每分钟只处理大约12批100条记录,而当进程首次启动时,它可以处理40批100条记录。

它定期刷新Session,并在每个RecordFormatter运行结束时关闭它(每个RecordFormatter都有自己的会话)。

我主要想了解Scala和Futures的任何已知问题。我注意到当它减速时,它似乎没有使用所有八个可能的线程,这可能可以解释速度下降,但我不明白为什么它会突然停止,而且总是在75000条记录左右。

谢谢!

编辑:更新代码以显示它使用yield和awaitAll,以防这有所区别。

3个回答

3
尝试限制演员库将创建的最大线程数(未来由演员支持)。 演员线程非常重,而在某些情况下,调度程序会像没有明天一样创建它们。 这使用了大量堆空间,并且可能使您的程序花费大量时间执行垃圾收集。 可以通过在命令行上设置actors.maxPoolSize参数来完成此操作...这将是类似于:-Dactors.maxPoolSize = 32或您想要的最大线程数。 我还强烈建议运行程序-Xprof以查看GC消耗的时间。

2
看起来像是内存问题。我建议获取一份内存使用情况的转储,看看它的行为。如果gc时间增长太多,那么你就找到了罪魁祸首。然后你可以增加JVM可用的内存来让程序再次运行。
无论如何,不要将转换成List。这是不必要的。如果你使用for/yield(在Scala 2.7上),那么转换成List才是必要的,但由于你没有yield任何东西,所以Range是更好的选择。

抱歉,我应该说明一下它确实会执行yield操作,因为它会保留Future列表,然后使用awaitAll()等待它们完成后再移动到下一个部分。内存使用可能是问题所在,但我不确定为什么它不会释放内存,因为我没有发现任何泄漏的地方。目前我分配了800M。 - Wysawyg
@Wysawyg - 你使用过 jconsole ($JAVA_HOME/bin/jconsole) 连接应用程序吗?这非常有助于告诉你一些事情:1. 应用程序是否花费了所有时间来执行 GC?2. 我的线程在做什么? - oxbow_lakes

2

jconsole应用程序随JDK捆绑安装(位于$JAVA_HOME/bin/jconsole),可用于连接到正在运行的应用程序。这非常适合告诉您以下几点:

  1. 应用程序是否将其所有时间都用于垃圾回收?
  2. 应用程序线程在做什么?

您能否在此处发布结果?


嘿,谢谢你的建议。我正在运行jconsole,但没有什么异常情况。GC在1小时8分钟的运行时间中已经花费了2分30秒。我也看不出线程在做什么方面有任何问题:我想我需要在它加速时进行分析,然后再次进行分析,一旦它开始爬行,就可以发现差异。感谢您的建议。 - Wysawyg
判断问题是否与 GC 有关的常规方法是观察 GC 数字是否开始实时上升。例如,现在可能是 60 中的 2 分钟,几分钟后可能变为 62 中的 4 分钟。这意味着最后的 2 分钟完全用于 GC。 - oxbow_lakes
例如,我有一个应用程序,在24小时内只花费不到3秒的时间进行GC!另一个应用程序(具有>20k个actor)在GC中占用超过10%的时间! - oxbow_lakes
我注意到的一件事是,负责线程管理的线程在等待方面花费的时间比起始时更多。 其他8个线程都标记为可运行,并且在阻塞中有大约1k的时间,没有在等待中,因此似乎这些线程已经准备好做工作了,但是工作并没有被分配给它们。 这听起来可信吗? 我现在正在尝试将完整记录数分成8批,并为每个批次启动一个actor,它们每个批次运行250次。这样我就可以看到是否与我的actor使用或其他代码有关。 - Wysawyg
好的 - 看起来这似乎是一个困难的问题。正如我所说,我有一个应用程序在实时处理所有地区的市场数据,涉及超过20,000个演员。这可能不是演员的错误。如果我是你,我会开始寻找像Hibernate正在进行的比较(可能是哈希冲突?)或者某些操作正在遍历每个批次调用上的所有结果的问题。 - oxbow_lakes
感谢您的所有帮助。如果我有足够的声望,我会为您的答案点赞。希望在我失去理智之前能解决这个问题。 - Wysawyg

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