scala2.9のparallel collection の benchmark をしてみた

話の発端はこんな感じ↓

#scala 合算する方法って、他にどんなのがあるだろう… http://www.ne.jp/asahi/hishidama/home/tech/scala/sample/sum.htmlless than a minute ago via Tween

そういえばparallel collection使えば速くなるんじゃ!less than a minute ago via Twitter for iPad

試したんですが上手く動かなくて http://www.ne.jp/asahi/hishidama/home/tech/scala/collection/par.html #scala RT @xuwei_k: そういえばparallel collection使えば速くなるんじゃ!less than a minute ago via Tween

というわけで、気になったのでやってみた↓

Welcome to Scala version 2.9.0.r24350-b20110228020058 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_24).
Type in expressions to have them evaluated.
Type :help for more information.

scala> scala.collection.parallel.availableProcessors // CPUは core i5だから、実際はコア数 2 ?
res0: Int = 4

scala> def benchmark(times: Int)(f: =>Any) = new scala.testing.Benchmark{ def run = f }.runBenchmark(times)
benchmark: (times: Int)(f: => Any)List[Long]

scala> implicit def benchmark2report(list:List[Long]) = new { val average = (list.sum - list.max - list.min).toDouble / (list.size-2) }
benchmark2report: (list: List[Long])java.lang.Object{val average: Double}

scala> def test(listSize:Int){
     |   println("--------------" + listSize + "---------------")
     |   val normal = 1L to listSize toList //普通のListつくる
     |   val parSeq = normal toParSeq       //要素が同じparalell版をつくる
     |   val normalResult = benchmark(5)( normal.reduceLeft(_ + _) )
     |   //reduceというのが、paralellなcollectionにしかないメソッドで、
     |   //内部でスレッド生成し分割して処理してるらしい
     |   val parSeqResult = benchmark(5)( parSeq.reduce(_ + _) )
     |   println( normalResult , parSeqResult , parSeqResult.average / normalResult.average )
     | }
test: (listSize: Int)Unit

scala> 2000000 to 20000000 by 2000000 foreach test
--------------2000000---------------
(List(334, 43, 45, 47, 44),List(28, 23, 22, 20, 20),0.47794117647058826)
--------------4000000---------------
(List(196, 70, 98, 76, 71),List(45, 45, 46, 44, 45),0.5510204081632653)
--------------6000000---------------
(List(1330, 159, 147, 161, 140),List(95, 74, 81, 99, 97),0.5845824411134904)
--------------8000000---------------
(List(722, 166, 168, 170, 159),List(107, 115, 114, 114, 111),0.6726190476190477)
--------------10000000---------------
(List(1104, 192, 202, 193, 197),List(132, 131, 129, 130, 125),0.6587837837837838)
--------------12000000---------------
(List(3089, 255, 274, 260, 233),List(164, 165, 165, 166, 159),0.6261089987325729)
--------------14000000---------------
(List(1365, 264, 265, 323, 249),List(176, 178, 180, 171, 171),0.6161971830985915)
--------------16000000---------------
(List(1726, 330, 300, 288, 292),List(220, 204, 219, 203, 209),0.6854663774403471)
--------------18000000---------------
(List(2147, 343, 335, 333, 326),List(231, 241, 228, 252, 245),0.7091988130563798)
--------------20000000---------------
(List(4329, 621, 549, 483, 474),List(322, 251, 258, 256, 255),0.46521476104053233)

パソコンのスペックは

  • Core i5-450M
  • メモリ 4GB
  • ubuntu10.10

あとJVMの引数のXmx を 4096M にしてます*1

reduceとreduceLeftについて、

  • 素数を200万から2000万まで200万きざみで変更
  • それぞれ5回ずつ計測して、最速と最遅を除いた3回分の平均を計算

しました

普通のListに対してreduceLeftするより、parallel collectionのほうが、だいたい60%くらいの時間に短縮されてる。
もっとコア数が4とか8個ぐらいあるマシンでやってみたい・・・

*1: REPLのデフォルトの256Mだとヒープ足りなくなる