いや、Scalaに詳しい人からすると当たり前かもしれませんが、今まで知らなかったので…。
通常、Scalaで例えばListの和を求めようと思うとfoldLeftとかreduceLeftとかを使うと思いますが、並列コレクションを使った場合って、これって並列に実行されると困りますよね?
実際、scala.collection.parallel.ParIterableLike.scalaを見ると、単純にオリジナルのシーケンスの各メソッドを呼んでいるだけになります。
// Source from Scala 2.9.2 ... def foldLeft[S](z: S)(op: (S, T) => S): S = seq.foldLeft(z)(op) def foldRight[S](z: S)(op: (T, S) => S): S = seq.foldRight(z)(op) def reduceLeft[U >: T](op: (U, T) => U): U = seq.reduceLeft(op) def reduceRight[U >: T](op: (T, U) => U): U = seq.reduceRight(op) def reduceLeftOption[U >: T](op: (U, T) => U): Option[U] = seq.reduceLeftOption(op) def reduceRightOption[U >: T](op: (T, U) => U): Option[U] = seq.reduceRightOption(op)
ここで、seqは並列コレクションの元となっているSeqです。
じゃあ、Scalaの並列コレクションでListの総和を求めたりするのを並列にできないの〜?ってことなんですが、例外がいました。
def fold[U >: T](z: U)(op: (U, U) => U): U = { executeAndWaitResult(new Fold(z, op, splitter)) } def reduce[U >: T](op: (U, U) => U): U = { executeAndWaitResult(new Reduce(op, splitter) mapResult { _.get }) }
他にも、いくつか並列に実行できるメソッドがありましたが、ここでは割愛。
ちなみに、単にListの総和を並列に求めたいだけなら、ParIterableLike#sumを使えばよいみたいです。
では、ちょっと使ってみましょう。こんなコードを用意。
object ParallelFold { def main(args: Array[String]): Unit = { printf("par.fold = %s%n", (1 to 10).par.fold("0")(_ + _.toString)) printf("par.foldLeft = %s%n", (1 to 10).par.foldLeft("0")(_ + _.toString)) } }
最初はParIterableLike#foldを使い、後ろはParIterableLike#foldLeftを使用しています。
コンパイルして、実行。
$ fsc ParallelFold.scala $ scala ParallelFold par.fold = 01020345060708910 par.foldLeft = 012345678910 $ scala ParallelFold par.fold = 010203450678910 par.foldLeft = 012345678910
なんか、実行する度に前者は微妙に結果が変わります。
なお、foldやreduceは並列コレクションでない場合はfoldLeft、reduceLeftと同じらしいですよ。ちょっと勉強になりました。
なんでこんなことを気にしたかって、ひとつ前にJavaでFork/Join Frameworkを使って遊んだ時に、「あれ…そういえば…」と思ったからです。