こんにちは、ピグディビジョンでFlashやってますnbhd(@nbhd)です。バイク乗りです。好きなゲームは鉄拳です。野ステ山ステは標準装備です。


FlashPlayer 11.4よりWorkerというマルチスレッド機能が搭載されました。従来の処理では処理落ちの原因になっていたような重い処理も、プログラムの実行を複数に分けることで解決できるようになります。使用するための条件はあるものの、描画処理と演算処理を別軸で実行できるのは非常に魅力的です。

今回はこのWorkerについてお話します。つい先日FlashBuilder 4.7 ベータ版がリリースされたので、併せてご確認ください。

Workerサンプルコード

Workerがどういったものか調べるためにサンプルコードをgithubに用意しました。
https://github.com/nbhd/1pixel/blob/master/worker/src/workertest.as
描画、演算ともに重い処理を記述してあります。
L85, L88のメソッドを切り替えることで、シングルスレッド、マルチスレッドの違いを確認することができます。

描画処理(400個のSpriteを同時に動かす)
private var seed:Number = 1;
private const MAX_BALL:int = 400;
private function enterFrameHandler(e:Event):void
{
   var i:int;
   var ball:Sprite;
   var radian:Number;
   seed += 0.00001;

   for (i = 0; i < MAX_BALL; i++)
   {
      ball = balls[i];
      radian = i * seed * Math.PI * ((1 + Math.sqrt(5)) * .5);
      ball.x += ((Math.cos(radian) * i) - ball.x) * .5;
      ball.y += ((Math.sin(radian) * i) - ball.y) * .5;
   }
}
演算処理 (for文で1億回まわす)
private const HARDCOST_LOOP:int = 100000000;
private function hardCostMethod():void
{
   var n:int = 0;
   var i:int;
   for (i = 0; i < HARDCOST_LOOP; i++)
   {
      n = i;
   }
}
まずはシングルスレッドで上述の処理を実行してみましょう。
singlethead
logに出力されている"left time"は起動までにかかった時間を示しています。演算処理でつっかえて、起動までに5870msかかっていることが分かります。
なんと重いのでしょうか。

今度は同じ処理をマルチスレッドで展開してみましょう。描画処理をメインスレッドで、演算処理をサブスレッドで実行した結果が以下のものになります。multithread1
なんと起動までにかかった時間は21msです。そして以下がサブスレッドの演算処理にかかった時間を受信したものになります。
multithread2
演算処理の終了までにかかった時間は5988msで、シングルスレッド版との差は118msほどマルチスレッド版のほうが遅い結果となりました。

まとめ

今回のケースではマルチスレッドを選択したほうがパフォーマンスの向上が得られることが分かりました。このようにマルチスレッドプログラミングは非常に難しいものではありますが、適切に使いこなすことが出来れば強い武器になります。プログラム全般に言えることですが、シンプルな設計、シンプルな実装を心がけておくことが重要です。

おまけ

スレッド間のデータのやりとりの一例をご紹介します。サブスレッドの準備が整ったところで、メインスレッドからサブスレッドに"worker ready"を通知し、それを受信したサブスレッドはメインスレッドに値を送り、その値をworkerToHandler内で加工しています。send()で値を順番に送り、receive()で値を順番に取得するこのやりかたは、ByteArrayでデータをやりとりする時のイメージと近いなと思いました。

private var mainTo:MessageChannel;
private var workerTo:MessageChannel;

// メインスレッドから送られてきたメッセージを処理する
private function mainToHandler(e:Event):void
{
     if (e.target.messageAvailable == false) return;
     var message:* = mainTo.receive();
     workerTo.send(message);

     if (message == 'worker ready')
     {
          workerTo.send(1);
          workerTo.send(2);
          workerTo.send(3);
     }
}

// worker の state を見張る
private function workerToHandler(e:Event):void
{
     if (e.target.messageAvailable == false) return;
     var message:* = workerTo.receive();

     if (message == 'worker ready')
     {
          // 送られてきた順に値を取得する
          var inc:int = workerTo.receive() + workerTo.receive() + workerTo.receive();
     }
}


[参考リンク]
http://cuaoar.jp/2012/07/actionscript-worker-flas.html
http://esdot.ca/site/2012/intro-to-as3-workers-hello-world