Scala并发速度变慢
Posted
技术标签:
【中文标题】Scala并发速度变慢【英文标题】:Scala Concurrency slowing down 【发布时间】:2010-12-24 02:01:29 【问题描述】:我这样做是为了说明我是一个相对的 Java/Scala 新手,所以我不排除有一些明显的事情我没有做。
我有一个 Scala 应用程序,它通过 Hibernate 连接到 mysql 数据库。该应用程序旨在处理大量数据,大约 2,750,000 条记录,因此我尝试尽可能优化它。
它在我的工作站上运行,该工作站是配备 6Gb RAM(1033Mhz)的 QuadCore Intel Xeon,它在前 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 条记录,然后另一个查询来拉回实际记录(在开始和结束值)然后将它们以 CSV 格式写入文本文件。查看计时器输出,记录格式化程序中的每个操作大约需要 5 秒来拉回记录,然后需要 0.1 秒来将其输出到文件。
尽管速度变慢了,但它每分钟只处理大约 12 批 100 条记录,而不是在流程刚开始时每分钟 40 批 100 条记录。
它会定期刷新 Session 并在每次 RecordFormatter 运行结束时关闭它(每个 RecordFormatter 都有自己的会话)。
我主要在寻找 Scala 和 Futures 的任何已知问题。我注意到,当它变慢时,它似乎并没有使用所有八个可能的线程,这当然可以解释速度下降,但对我来说,为什么它会突然停止并始终保持在 75k 记录标记附近是个谜。
谢谢!
编辑:更新代码以显示它使用 yield 和 awaitAll 以防万一。
【问题讨论】:
【参考方案1】:看起来像是内存问题。我会得到内存使用情况的转储,看看它是如何表现的。如果 gc 时间增加太多,那么你就是罪魁祸首。然后,您可以只增加 JVM 可用的内存以使其再次运行。
无论如何,不要将batches
转换为列表。这是不必要的。如果您使用 for
/yield
(在 Scala 2.7 上),这将是必要的,但由于您没有产生任何东西,那么 Range
是一个更好的选择。
【讨论】:
对不起。我应该包括它确实会产生收益,因为它保留了 Futures 的列表,然后执行 awaitAll() 以等待它们完成,然后再进入下一部分。内存使用可能是问题,但我不确定为什么它不会释放内存,因为我无法发现任何泄漏。我按照目前的状态分配 800M。 @Wysawyg - 您是否使用jconsole
($JAVA_HOME/bin/jconsole
) 附加到应用程序?这很适合告诉您一些事情: 1. 应用程序是否将所有时间都花在了 GC 上? 2. 我的线程在做什么?【参考方案2】:
与 JDK 捆绑在一起的 jconsole
应用程序(在 $JAVA_HOME/bin/jconsole
中)可用于在应用程序运行时附加到应用程序。这很适合告诉你一些事情:
-
应用程序是否将所有时间都花在了 GC 上?
应用程序线程在做什么?
你能把结果贴在这里吗?
【讨论】:
您好,谢谢您的建议。我正在运行 jconsole,但没有什么比这更糟糕的了。到目前为止,GC 在 1 小时 8 分钟的运行时间中花费了 2 分钟 30 秒。我也看不出线程在做什么:我想我需要在它加速时对其进行分析,然后在它开始爬行时再次对其进行分析并找出差异。感谢您的建议。 如果问题是 GC,发现的正常情况是 GC 数量在现实中开始或多或少地上升。即现在可能是 60 分钟中的 2 分钟,几分钟后可能是 62 分钟中的 4 分钟。这意味着最后 2 分钟完全花费在 GC 中 举个例子,我有一个应用程序在 24 小时内在 GC 中花费的时间不到 3 秒!另一个(拥有超过 2 万名演员)在 GC 中占用了超过 10% 的时间! 我注意到的一件事是负责线程管理的线程大部分时间都花在等待上,比开始时更多。所有其他 8 个线程都被标记为 Runnable 并且有大约 1k 时间处于阻塞状态并且没有等待时间,所以看起来这些线程已准备好执行工作,但不知何故没有将工作分配给它们。这听起来有道理吗?我现在正在尝试将完整的记录计数分成 8 个批次,并为每个批次解雇一个演员,他们每 250 个批次运行。这样我就可以查看是否与我使用演员或其他代码有关。 好的 - 所以这看起来很困难。正如我所说,我有一个运行超过 20,000 个参与者的应用程序,实时处理所有地区的市场数据。这可能不是演员的错误。如果我是你,我会开始寻找 Hibernate 进行的比较(可能是哈希冲突?)之类的东西,或者在每次批处理调用时遍历所有结果的某些操作【参考方案3】:尝试限制 actor 库将创建的最大线程数(future 由 actor 支持)。演员线程是非常重量级的,在某些情况下,调度程序会创建它们,就像没有明天一样。这会占用大量堆空间,并且会使您的程序花费大量时间执行垃圾回收。
这可以通过在命令行中设置actors.maxPoolSize 参数来完成...类似于:-Dactors.maxPoolSize=32 或任何您想要的最大线程数。
我还强烈建议运行您的程序 -Xprof 以查看 GC 消耗了多少时间。
【讨论】:
以上是关于Scala并发速度变慢的主要内容,如果未能解决你的问题,请参考以下文章