CLOVER🍀

That was when it all began.

Scalaでコレクションを並列に畳み込む

いや、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を使って遊んだ時に、「あれ…そういえば…」と思ったからです。