小笠原 啓
有限会社ITプランニング勤務のプログラマー。Scala、OCaml、F#などの静的型付け関数型言語を利用したシステム開発業務に従事。定理証明支援器Coqやモデル発見器Alloyといった形式手法ツールの業務への応用にも興味を持っている。

 前回は、アクターとはメッセージ受信ループを内包した並行に動く計算主体であること、Scalaにはアクタースタイルの並行プログラミングをサポートする基本機能やタイムアウト、Futureなどの便利な機能が備わっていることをご紹介しました。

 今回は、アクターを実戦に投入するに当たって知っておきたい、パフォーマンスに関する議論について解説します。やや詳細に踏み込んでいきますが、「アクターには飽きたー」などと言わず、最後までお付き合いください。

 なお、本稿ではScala Version 2.8.0を前提とします。Version 2.7.7のアクター用タスクスケジューラーは本稿の説明とは若干異なる動きをしますので、旧バージョンでの動作に興味がある方は、本稿最後のカコミ記事をご覧ください。

アクターとスレッドの関係

 アクターの並列性は、JavaVMのスレッドを使って実装されています。このことはロックフリーなアクタースタイルでプログラムを組む上では特に考える必要がなく、むしろその下位構造を意識しなくてもいい高度な抽象化がアクターの強力さの源泉でもあります。

 しかし、実際のプログラム開発においてアクターを使うとなると、その実行パフォーマンスやリソース消費について知らない訳にはいきません。

 例えば、アクターとスレッドは一対一に対応するのでしょうか? そうだとすると、アクターは気軽に生成できない高コストなオブジェクトであり、アプリケーション全体で利用するアクターの数を気にする必要がでてきます。

 そこで、リスト1のような簡単な実験をしてみます。

リスト1●250個のアクターを動作させるコード
import scala.actors.Actor
import scala.actors.Actor._

case object Show

// Showメッセージを受け取ったら、スレッド情報を表示して終わるアクター
class ShowActor extends Actor {
    def act() {
  receive {
      case Show =>
    var t = Thread.currentThread()
    println("id = " + t.getId() + ", name = " + t.getName()) // スレッドIDと名前を表示
  }
    }
}

object App {
    def main(args:Array[String]) {
  val rts = List.range(0, 250) map { _ => new ShowActor } // 250個のアクターを生成
  rts foreach { _.start } // アクター実行
  rts foreach { _ ! Show } // Showメッセージの送信
    }
}

 リスト1では、Showメッセージを受け取ったら、カレントスレッドのID番号と名前を表示して終わるアクターを250個作り、そのそれぞれにShowメッセージを送っています。もしアクターとスレッドが一対一対応するなら、このプログラムを実行すると、250個のスレッドIDが画面に羅列されるはずです。

 リスト1実行結果は次のようになります。

$ scalac list1.scala                  <- コンパイル
$ scala -cp . App | sort | uniq       <- 実行
id = 10, name = ForkJoinPool-1-worker-1
id = 11, name = ForkJoinPool-1-worker-2
id = 12, name = ForkJoinPool-1-worker-3
id = 9, name = ForkJoinPool-1-worker-0

 私の端末環境では、4種類のスレッドIDと名前が表示されました。つまり、250個のアクターを動作させたにもかかわらず、実際にアクターに割り当てられたスレッドは4種類だったということになります。実行環境によっては4種類だけではなく、もっと多くの種類のスレッドIDと名前が表示されることもありますが、250個よりは少ないはずです。

 表示されたスレッドの名前からも想像できますが、実はScalaのアクターライブラリは環境に合わせた個数(最低でも、4個もしくはCPU数*2個のうち大きい方)のワーカースレッドを自動的に作成し、複数のアクターの実行のために少数のワーカースレッドを使い回します。

 そもそもreceive関数によってメッセージ待ちになったアクターは、スレッドを占有している必要はありません。次回メッセージが届いたら動き出せばいいのですから、その間は別の処理をしていても構わないはずです。

 Scalaのアクターライブラリにはこのようなスレッドの使い回しの仕組みがあり、利用する側としては多くのアクターを生成してもスレッドリソースの無駄遣いを気にしなくてもよいようになっています。

 なお、動的に生成されるスレッドの個数の上限は、システムプロパティ actors.maxPoolSize で設定することができ、この値はデフォルトでは 256 になっています。もし256個以上の並列性が必要な場合には、この値を増やしておきましょう。