Parallel Collectionのベンチマークやってみました

Scala2.9で導入されるParallel Collectionについてベンチマークがなされており、Scala2.9を触ったことなかったのでこれに乗じてみようかと。
scala2.9のparallel collection の benchmark をしてみた - scalaとか・・・
Parallel Collectionのベンチマークやってみた - k4200’s notes and thoughts

準備

Scalaのバージョンを簡単に切り替えられる"svm"ってヤツ作った (冷やし中華終わりました) - ゆるよろ・オブ・ザ・( ;゚皿゚)ノシΣ フィンギィィーーッ!!! 日記
Scalaのインストールに@yuroyoroさんのsvmを利用してます。

svm update-latest

これでnightly-buildをダウンロードしてくれます。DL後にcurrentを切り替えるか聞かれるのでyesで。

> scala -version
Scala code runner version 2.9.0.r24471-b20110317020039 -- Copyright 2002-2011, LAMP/EPFL

2.9入りました。

JAVA_OPTSは例に習って以下を設定

export JAVA_OPTS='-Xmx4G -Xms1G'

PCスペック

つい最近新調しました。初MacですがMac使いやすいですね。
このPCの性能を見てみたかったとかそういうこともあります。

ソース

元のものをobjectに突っ込んでコンパイルできるようにしただけです。

object ParBench {
  def benchmark(times: Int)(f: =>Any) = new scala.testing.Benchmark{ def run = f }.runBenchmark(times)

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

  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 )
  }

  def main(args: Array[String]) {
    println("availableProcessors: " + scala.collection.parallel.availableProcessors)
    2000000 to 20000000 by 2000000 foreach test
  }
}

これをファイルに保存してscalac。

さっそく実行

ファンがガンガン回ってPC酷使してる感じがしますね。

availableProcessors: 4
--------------2000000---------------
(List(39, 24, 13, 13, 14),List(42, 12, 12, 12, 12),0.7058823529411765)
--------------4000000---------------
(List(72, 28, 29, 29, 28),List(25, 25, 26, 24, 27),0.883720930232558)
--------------6000000---------------
(List(132, 43, 43, 44, 44),List(41, 39, 41, 40, 40),0.9236641221374047)
--------------8000000---------------
(List(165, 57, 58, 57, 57),List(52, 52, 51, 51, 52),0.9011627906976744)
--------------10000000---------------
(List(154, 80, 85, 84, 83),List(70, 71, 70, 70, 70),0.8333333333333334)
--------------12000000---------------
(List(183, 97, 99, 103, 102),List(85, 88, 87, 85, 86),0.8486842105263158)
--------------14000000---------------
(List(212, 120, 118, 118, 118),List(100, 99, 98, 100, 100),0.8398876404494382)
--------------16000000---------------
(List(252, 140, 140, 140, 138),List(114, 117, 118, 117, 116),0.8333333333333334)
--------------18000000---------------
(List(181, 158, 158, 158, 158),List(135, 133, 137, 135, 132),0.8502109704641351)
--------------20000000---------------
(List(195, 173, 176, 171, 174),List(149, 144, 146, 150, 148),0.8470363288718928)

あれ?
0.8台半ばで期待したほど早くならない。

REPLで実行

元と違うのはscalacでコンパイルして実行しているとこ、なのでREPLで実行してみる。

Welcome to Scala version 2.9.0.r24471-b20110317020039 (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> :load ParBench.scala
Loading ParBench.scala...
defined module ParBench

availableProcessors: 4
--------------2000000---------------
(List(29, 23, 23, 23, 23),List(42, 11, 11, 10, 10),0.46376811594202894)
--------------4000000---------------
(List(101, 47, 47, 47, 49),List(28, 26, 26, 27, 27),0.5594405594405595)
--------------6000000---------------
(List(175, 74, 72, 73, 72),List(40, 41, 42, 41, 40),0.5570776255707762)
--------------8000000---------------
(List(208, 96, 96, 96, 96),List(53, 54, 54, 52, 53),0.5555555555555556)
--------------10000000---------------
(List(221, 146, 142, 145, 142),List(73, 70, 72, 71, 70),0.4919168591224018)
--------------12000000---------------
(List(272, 176, 172, 178, 174),List(87, 89, 86, 88, 86),0.4943181818181818)
--------------14000000---------------
(List(299, 200, 204, 204, 194),List(102, 100, 100, 101, 100),0.49506578947368424)
--------------16000000---------------
(List(367, 245, 235, 231, 243),List(118, 119, 117, 119, 114),0.4896265560165975)
--------------18000000---------------
(List(309, 268, 273, 271, 271),List(136, 135, 138, 136, 134),0.4993865030674846)
--------------20000000---------------
(List(336, 306, 305, 304, 313),List(149, 149, 149, 150, 149),0.4837662337662338)

0.5前後で倍率はよくなってるけどこれは……

並べると

--------------20000000---------------
(List(195, 173, 176, 171, 174),List(149, 144, 146, 150, 148),0.8470363288718928)  // コンパイル
(List(336, 306, 305, 304, 313),List(149, 149, 149, 150, 149),0.4837662337662338)  // REPL

parallelの速度は変わってないですが、normalはコンパイルするとかなり速度が向上してる。
これはJITコンパイラが頑張ってくれてるということかな。

Server VM

そういえばREPLはServer VMで起動してますね。
という訳でJAVA_OPTSに -server を追加してみて再度実行。

export JAVA_OPTS='-server -Xmx4G -Xms1G'
availableProcessors: 4
--------------2000000---------------
(List(39, 13, 12, 13, 14),List(39, 10, 10, 10, 9),0.75)
--------------4000000---------------
(List(453, 30, 42, 30, 30),List(22, 21, 22, 22, 22),0.6470588235294118)
--------------6000000---------------
(List(93, 41, 41, 41, 41),List(29, 28, 29, 28, 29),0.6991869918699187)
--------------8000000---------------
(List(2082, 60, 61, 61, 59),List(42, 42, 42, 42, 42),0.6923076923076923)
--------------10000000---------------
(List(2632, 74, 75, 74, 74),List(52, 52, 52, 51, 51),0.695067264573991)
--------------12000000---------------
(List(147, 84, 85, 85, 83),List(58, 56, 58, 57, 57),0.6771653543307087)
--------------14000000---------------
(List(148, 100, 98, 99, 100),List(67, 67, 67, 67, 66),0.6722408026755853)
--------------16000000---------------
(List(3940, 121, 125, 122, 123),List(82, 81, 82, 82, 83),0.6648648648648648)
--------------18000000---------------
(List(4628, 138, 139, 137, 138),List(95, 95, 95, 95, 95),0.6867469879518072)
--------------20000000---------------
(List(3829, 152, 151, 149, 150),List(123, 124, 119, 119, 119),0.7969094922737306)
--------------20000000---------------
(List(195, 173, 176, 171, 174),List(149, 144, 146, 150, 148),0.8470363288718928)    // コンパイル
(List(336, 306, 305, 304, 313),List(149, 149, 149, 150, 149),0.4837662337662338)    // REPL
(List(3829, 152, 151, 149, 150),List(123, 124, 119, 119, 119),0.7969094922737306)  // コンパイル(Server VM)

更に高速化しました。
normalの最初で極端に遅くなるのはGCっぽいかな?-Xmsは-Xmxと同じにした方がいいかも。REPLではここまでひどい数値は出ないので他に何かあるんだと思いますが。

まとめ

JITコンパイラさん(多分)ありがとう!
畳み込み以外とかtoParSeq以外とかも試してみたいですね。