为什么使用复制比串行执行慢得多?

时间:2010-10-23 17:14:00

标签: scala concurrency scala-2.8

我有点问题。我想使用scala.concurrent.ops.replicate来并行化我的程序。但我发现,算法实际上变得慢得多。 所以我写了一个小测试,但仍然得到了相同的结果。所以他们在这里。

序列号:完成约63秒

object SerTest {
  def main(args: Array[String]) {
      for(x <- 1 to 10){
        for(i <- 1 to 4) {
          for(j <- 1 to 100000) {
            val a = BigInt(j).isProbablePrime(1000)
            if(!a && j == 100000) println(i + " is ready")}}}}}

并发代码:完成大约161秒

object ParTest {
  def main(args: Array[String]) {
      for(x <- 1 to 10){
        replicate(1,5) { i =>
          for(j <- 1 to 100000) {
            val a = BigInt(j).isProbablePrime(1000)
            if(!a && j == 100000) println(i + " is ready")}}}}}

那么我所犯的那个完全明显且令人尴尬的错误在哪里? :)

编辑:哦,我在Quadcore-CPU上运行它。所以它实际上应该更快:)

编辑2:由于凯文赖特的回答,我稍微改变了程序,以便有更长的时间来运行。

2 个答案:

答案 0 :(得分:3)

查看BigInteger.isProbablePrime的源代码(BigInt委托给java库)。它正在做大量的新BigInteger()因为那是一个不可变的类。

我的猜测是内存分配导致太多争用,从并行化中受益。您可以通过将一个简单的计算(例如将100MM数相乘)替换为主要测试来确认。或者,使用var longs而不是BigInt重写主要测试。

此外,ops.replicate会将操作生成新线程,而不是使用某种线程池。线程创建有一定的开销,但在这种情况下不足以成为问题。我个人更喜欢坚持使用更强大的java.util.concurrent库。

答案 1 :(得分:2)

查看示例代码,我猜你是从命令行直接跳到main方法。这是您在Java中进行微抽样的绝对最糟糕的方式!

您应该首先运行您的测试几次(在同一个VM调用中),至少足以使JVM在您思考之前已经正常预热并运行了30秒关于开始测量任何东西。这将确保它运行已编译(而非解释)的代码,并且已经完全优化。

您还需要了解启动线程的成本。对于短时间运行的循环,这将是一个令人望而却步的开销,并且将比循环本身消耗更多的时间!

<强>更新

以下定义来自ops.scala:

val defaultRunner: FutureTaskRunner = TaskRunners.threadRunner
def spawn(p: => Unit)(implicit runner: TaskRunner = defaultRunner): Unit = {...}
def replicate(start: Int, end: Int)(p: Int => Unit) {...}

所以使用的实际跑步者是一个隐含的, 或默认为TaskRunners.threadRunner

您可以尝试通过为代码添加前缀来更改此选项以使用线程池:

implicit val runner = TaskRunners.threadPoolRunner

或者相信以下内容也适用:

import concurrent.TaskRunners.threadPoolRunner

看看是否有任何区别


再想一想......

我认为该参数实际上不会传递给嵌套调用spawn,如果您自己只复制该方法可能会更好(我目前在邮件列表上有一个关于此的查询) )。

为了您的方便,这里有完整,可怕,荣耀的方法:

def replicate(start: Int, end: Int)(p: Int => Unit) {
  if (start == end) 
    ()
  else if (start + 1 == end)
    p(start)
  else {
    val mid = (start + end) / 2
    spawn { replicate(start, mid)(p) }
    replicate(mid, end)(p)
  }
}

(你仍然需要定义隐含的跑步者......)