使用和不使用`par`(提供的代码段)时为什么会有这么大的差异?

时间:2013-08-28 17:16:58

标签: scala parallel-processing

运行此程序会显示以下结果:

object ParallelTest {
  def main(args: Array[String]) {
    val start = System.nanoTime()
    val list = (1 to 10000).toList.par
    println("with par: elapsed: " + (System.nanoTime() - start) / 1000000 + " milliseconds")

    val start2 = System.nanoTime()
    val list2 = (1 to 10000).toList
    println("without par: elapsed: " + (System.nanoTime() - start2) / 1000000 + " milliseconds")
  } 
}

with par: elapsed: 238 milliseconds 
without par: elapsed: 0 milliseconds

如果我理解这些结果,使用par会花费更长时间,因为“并行化”List需要将内容复制到并行数据结构?

3 个答案:

答案 0 :(得分:1)

当我将其加载到我的REPL并执行ParallelTest.main(Array())两次时:

scala> ParallelTest.main(Array())
with par: elapsed: 23 milliseconds
without par: elapsed: 1 milliseconds

scala> ParallelTest.main(Array())
with par: elapsed: 1 milliseconds
without par: elapsed: 0 milliseconds

您所看到的几乎所有内容都是JIT热身。 Hotspot在第一个循环之后优化了相关方法,我们在接下来的三次迭代中看到了好处。对JVM进行适当的基准测试需要丢掉前几个结果。

答案 1 :(得分:1)

我对作为下一个黑客的毫无意义的微基准测试同样好奇,所以这里是为什么结果有意义,为什么重要的是你放par以及为什么OP的推测是正确的(如果方法论存在缺陷):

scala> import System.nanoTime
import System.nanoTime

scala> def timed(op: =>Unit) = { val t0=nanoTime;op;println(nanoTime-t0) }
timed: (op: => Unit)Unit

scala> val data = (1 to 1000000).toList
data: List[Int] = List(1, 2, 3, 4,...

scala> timed(data.par)
85333715

scala> timed(data.par)
40952638

scala> timed(data.par)
40134628

在我的机器上,构建一个小的10k列表需要与调用它上面的par相同的时间,大约400k nanos,这就是为什么,在绿色检查答案中,.toList.par向上舍入到一个.toList向下舍入为零。

OTOH,按顺序构建一个大的1m列表更加可变。

scala> 1 to 100 foreach (_ => timed((1 to 1000000).toList))

在某处丢失了十分之一。我没有看到是否是由于重新分配,垃圾收集,内存架构或什么。

但有趣的是这很容易:

scala> 1 to 100 foreach (_ => timed((1 to 1000000).par.to[ParVector]))

ParRange在此测试中排除了顺序Range,并且比data.par更快。 (在我的机器上。)

对我来说有趣的是,这里没有计算并行化。

这必须意味着并行组装ParVector的成本很低。比较this other answer并列groupBy中汇编的成本对我来说是ParNewbie令人惊讶。

答案 2 :(得分:0)

其他人已经注意到由于非确定性的预热不确定性而难以在JVM上进行微基准测试。我想提出一个不同的话题。

并行集合框架需要谨慎使用。所有通过并行化提高软件速度的尝试均受Amdahl's Law的约束:使用并行处理器的程序的加速受到程序序列部分所需时间的限制。

因此,只有当可能使用它们的真实应用程序可靠(并且始终如一)进行基准测试以确定哪些部分值得并行尝试以及哪些部分不是并行时,才应用并行集合。幸运的是,在并行和顺序集合之间切换以比较它们的使用相对容易。

此外,使用并行程序来提高速度是使用并发来表达解决方案的一个相关但不同的问题。 Actors在Scala中提供此功能。 GoOccam和其他语言依赖于CSP通信流程体系结构,而不是提供更细粒度和基于数学的并发表达式(目前还有在Scala中支持CSP的工作) )。通常,并发程序比并行集合的顺序程序更适合并行处理,主要是因为Amdahl定律。并行集合仅适用于相对较大的数据集和每个元素相对较重的处理负载。