【问题标题】:Strange Behaviour Using Scala Parallel Collections and setParallelism使用 Scala 并行集合和 setParallelism 的奇怪行为
【发布时间】:2011-09-02 12:02:34
【问题描述】:

我最近发现了 Scala 2.9 中的 Parallel Collection,并且很高兴看到可以使用 collection.parallel.ForkJoinTasks.defaultForkJoinPool.setParallelism 设置并行度。

但是,当我尝试添加两个大小为一百万的向量的实验时,我发现

  • 使用并行收集并将并行度设置为 64 与顺序收集一样快(显示在结果中)。
  • 增加 setParallelism 似乎会以非线性方式提高性能。我至少会有预期的单调行为(也就是说,如果我增加并行度,性能不会降低)

谁能解释一下为什么会这样

  object examplePar extends App{    

  val Rnd = new Random()
  val numSims = 1

  val x = for(j <- 1 to 1000000) yield Rnd.nextDouble()
  val y = for(j <- 1 to 1000000) yield Rnd.nextDouble()

  val parInt = List(1,2,4,8,16,32,64,128,256)    
  var avg:Double = 0.0
  var currTime:Long = 0

  for(j <- parInt){
    collection.parallel.ForkJoinTasks.defaultForkJoinPool.setParallelism(j)
    avg = 0.0
    for (k <- 1 to numSims){
      currTime = System.currentTimeMillis()    
      (x zip y).par.map(x => x._1 + x._2)
      avg += (System.currentTimeMillis() - currTime)
    }
    println("Average Time to execute with Parallelism set to " + j.toString + " = "+ (avg/numSims).toString + "ms")    
  }

  currTime = System.currentTimeMillis()
  (x zip y).map(x => x._1 + x._2)
  println("Time to execute using Sequential = " + (System.currentTimeMillis() - currTime).toString + "ms")        
}

使用 Scala 2.9.1 和四核处理器运行示例的结果是

Average Time to execute with Parallelism set to 1 = 1047.0ms
Average Time to execute with Parallelism set to 2 = 594.0ms
Average Time to execute with Parallelism set to 4 = 672.0ms
Average Time to execute with Parallelism set to 8 = 343.0ms
Average Time to execute with Parallelism set to 16 = 375.0ms
Average Time to execute with Parallelism set to 32 = 391.0ms
Average Time to execute with Parallelism set to 64 = 406.0ms
Average Time to execute with Parallelism set to 128 = 813.0ms
Average Time to execute with Parallelism set to 256 = 469.0ms
Time to execute using Sequential = 406ms

虽然这些结果是针对一次运行的,但在多次运行的平均时它们是一致的

【问题讨论】:

  • 如果您尝试设置比可用处理器更多的并行度,您将让这些东西彼此串行运行,但仍需支付并行度税。

标签: scala parallel-processing


【解决方案1】:

并行性不是免费的。它需要额外的周期来将问题分成更小的块、组织所有内容并同步结果。

你可以把这想象成打电话给你所有的朋友帮你搬家,等他们到那里,帮你装卡车,然后带他们出去吃午饭,最后,继续你的任务。

在您的测试用例中,您要添加两个双精度数,这是一个微不足道的练习,并且花费的时间非常短,以至于并行化的开销比简单地在一个线程中执行任务要大。

再一次,类比是打电话给你所有的朋友帮你搬 3 个手提箱。摆脱它们需要你半天的时间,而你可以在几分钟内自己完成。

要从并行化中获得任何好处,您的任务必须足够复杂以保证额外的开销。尝试做一些昂贵的计算,例如一个包含 5-10 个三角函数和对数函数的公式。

【讨论】:

  • 谢谢。我喜欢你的类比:-)。我将评估的函数替换为更复杂的函数(使用在循环中评估的一系列嵌套三角函数)。我现在可以看到非并行实现的 2.5 倍加速。但是当增加并行度时,性能下降仍然是出乎意料的和令人失望的:-(但正如你所说,开销可能随着并行度的增加而增加。
【解决方案2】:

我建议研究和使用scala.testing.Benchmark trait 来对代码的 sn-ps 进行基准测试。在 JVM 上进行基准测试时,您必须考虑 JIT、GC 和其他因素 - 请参阅 this paper。简而言之,在进行几次热身运行之后,您必须在单独的 JVM 中进行每次运行。

另外,请注意 (x zip y) 部分不会并行发生,因为 xy 尚未并行 - zip 是按顺序完成的。接下来,我建议将 xy 转换为数组 (toArray),然后调用 par - 这将确保基准测试使用并行数组,而不是并行向量(对于诸如zipmap)。

【讨论】:

  • 谢谢,作为一名统计学家,我喜欢这篇论文的严谨性,我会按照它对 Scala 的并行性进行更严格的评估
猜你喜欢
  • 1970-01-01
  • 2019-12-22
  • 1970-01-01
  • 2016-03-17
  • 1970-01-01
  • 2023-03-31
  • 2011-05-09
  • 1970-01-01
  • 2013-03-10
相关资源
最近更新 更多