HTML5 の Web Workersを試してみる

Webが好きな自分としてはHTML5は新たな可能性を感じずにはいられない!
今更だけどこれからいろいろ試してみようと思う。
HTML5の中でも今までJavascriptでは出来なかった
マルチスレッドでの処理ができるWeb Workersが気になって試してみた。


注)javascriptの仕様を中途半端に知ってる人なのでおかしいところがあるかも。


さっそく何か書いてみる。
環境はWindows版Safari5。実験結果には責任は持たないw

テスト1
<!DOCTYPE html>
  <head>
    <meta charset="utf-8" />
    <title>Web Workersを試してみる</title>
    <script>
      
      onload = function(){
        var showDiv = document.getElementById("show");
        
        document.getElementById("button").addEventListener("click", function(){
          var start = new Date();

          showDiv.innerHTML = "";
          
          // Web Workers
          var myWorker = new Worker("work.js");
          myWorker.addEventListener("message", function(event){
            showDiv.innerHTML += event.data + ":" +(new Date() - start)+"msec.<br />";
          }, true);
          
          myWorker.postMessage("1");
          

        }, true);
      
      }
    </script>
  </head>
  <body>
    <h1>Web Workersを使う</h1>
    <form action="./" method="get">
      <input type="button" id="button" value="doTest()" />
    </form>
    <div id="show"></div>
    
  </body>
</html>

new Worker("work.js");でワーカーオブジェクトを作成。
引数はワーカーオブジェクト用に作った別のjsファイル。
"message"のイベントリスナーをセットしたらpostMessageメソッドでワーカースレッドが動作する。


work.jsファイルの中身。

addEventListener("message", function(event){
	//重たい処理
	for(var i = 0; i < 1000000; i++){
		new Date();
	}
    postMessage(event.data);
}, false);

こっちも"message"イベントを受け取って
postMessageでUIスレッドに結果のメッセージを返すように作る。
event.dataにはpostMessageの引数が入っている。

結果

Web Workersを使う


1:244msec.

ボタンを押したあとワーカースレッドで処理が行われ、
その後処理結果が表示される。時間はボタンを押してからの処理時間。


今度はWeb Workersを使った場合と
普通にUIスレッドで同じ処理を実行した場合を比較してみる。

スト2
<!DOCTYPE html>
  <head>
    <meta charset="utf-8" />
    <title>Web Workersを試してみる</title>
    <script>
      
      onload = function(){
        var showDiv1 = document.getElementById("show1");
        var showDiv2 = document.getElementById("show2");
        
        document.getElementById("button1").addEventListener("click", function(){
          var start = new Date();
          var mListener = function(event){
            showDiv1.innerHTML += event.data + ":" +(new Date() - start)+"msec.<br />";
          };

          showDiv1.innerHTML = "";
          
          // Web Workers
          var myWorker1 = new Worker("work.js");
          myWorker1.addEventListener("message", mListener, true);
          
          var myWorker2 = new Worker("work.js");
          myWorker2.addEventListener("message", mListener, true);
          
          myWorker1.postMessage("WW1");
          myWorker2.postMessage("WW2");
          

        }, true);
        
        document.getElementById("button2").addEventListener("click", function(){
          var start = new Date();
          var mListener = function(event){
            showDiv2.innerHTML += event.data + ":" +(new Date() - start)+"msec.<br />";
          };

          showDiv2.innerHTML = "";
          
          //普通(?)の処理(UIスレッドで)
          work = function(arg){
            //重たい処理
            for(var i = 0; i < 1000000; i++){
              new Date();
            }
            mListener(arg);
          };
          work({data:"普通1"});
          work({data:"普通2"});
          
        }, true);
      }
    </script>
  </head>
  <body>
    <h1>Web Workersの並列処理</h1>
    <form action="./" method="get">
      <input type="button" id="button1" value="doTest1()" />
      <input type="button" id="button2" value="doTest2()" />
    </form>
    <div id="show1"></div>
    <div id="show2"></div>
    
  </body>
</html>

work.js

addEventListener("message", function(event){
	//重たい処理
	for(var i = 0; i < 1000000; i++){
		new Date();
	}
    postMessage(event.data);
}, false);
結果

Web Workersの並列処理


WW1:257msec.
WW2:261msec.
普通1:368msec.
普通2:732msec.

2種類の処理を別々に実行した結果の比較。
Web Workersを使った処理ではUIスレッドで処理するより断然速い?




スレッドの数をもっと増やしてみよう。

テスト3
<!DOCTYPE html>
  <head>
    <meta charset="utf-8" />
    <title>Web Workersを試してみる</title>
    <script>
      
      onload = function(){
        var showDiv = document.getElementById("show");
        
        document.getElementById("button").addEventListener("click", function(){
          var start = new Date();
          var mListener = function(event){
            showDiv.innerHTML += event.data + ":" +(new Date() - start)+"msec.<br />";
          };

          showDiv.innerHTML = "";
          
          // Web Workers
          var myWorker1 = new Worker("work.js");
          myWorker1.addEventListener("message", mListener, true);
          
          var myWorker2 = new Worker("work.js");
          myWorker2.addEventListener("message", mListener, true);
          
          var myWorker3 = new Worker("work.js");
          myWorker3.addEventListener("message", mListener, true);
          
          var myWorker4 = new Worker("work.js");
          myWorker4.addEventListener("message", mListener, true);
          
          var myWorker5 = new Worker("work.js");
          myWorker5.addEventListener("message", mListener, true);
          
          var myWorker6 = new Worker("work.js");
          myWorker6.addEventListener("message", mListener, true);
          

          myWorker1.postMessage("1");
          myWorker2.postMessage("2");
          myWorker3.postMessage("3");
          myWorker4.postMessage("4");
          myWorker5.postMessage("5");
          myWorker6.postMessage("6");
        }, true);
      }
    </script>
  </head>
  <body>
    <h1>Web Workers並行処理のテスト</h1>
    <form action="./" method="get">
      <input type="button" id="button" value="doTest()" />
    </form>
    <div id="show"></div>
    
  </body>
</html>
結果

Web Workers並行処理のテスト


1:638msec.
3:649msec.
2:694msec.
4:708msec.
6:751msec.
5:771msec.

結果2

Web Workers並行処理のテスト


3:675msec.
2:689msec.
1:690msec.
5:743msec.
4:746msec.
6:796msec.

100万回new Date()する処理。
マルチスレッドで動いてることが確認できる。


ワーカースレッドからグローバル変数を参照しようとしてみる。

テスト4
<!DOCTYPE html>
  <head>
    <meta charset="utf-8" />
    <title>Web Workersを試してみる</title>
    <script>
      
      var globalObj = "Test";
      
      onload = function(){
        var showDiv = document.getElementById("show");
        
        document.getElementById("button").addEventListener("click", function(){
          
          var dataObj = {data:"Some data"};
          
          var mListener = function(event){
            showDiv.innerHTML += dataObj.data + " ⇔ " + event.data.data +"<br />";
          };

          showDiv.innerHTML = "";
          

          var myWorker1 = new Worker("work.js");
          myWorker1.addEventListener("message", mListener, true);
          
          
          myWorker1.postMessage(dataObj);
          
        }, true);
      }
    </script>
  </head>
  <body>
    <h1>Workerスレッドからの参照</h1>
    <form action="./" method="get">
      <input type="button" id="button" value="doTest()" />
    </form>
    <div id="show"></div>
    
  </body>
</html>

work.jsをちょっと書き換える。

addEventListener("message", function(event){

	if(globalObj == null){}
	
}, false);

実行すると

ReferenceError: Can't find variable: globalObj

エラーが出る。
というか、ちょっと考えれば当然の結果だった。
それじゃあ、eventの引数について調べてみる。


引数eventはオブジェクトが渡ってきていてevent.dataに渡されたオブジェクトが入っている。
それを書き換えてみるけど・・・

addEventListener("message", function(event){
     
	var obj = event.data;
	obj.data = "data has been changed";
	
    postMessage(obj);
}, false);

Workerスレッドからの参照


Some data ⇔ data has been changed

いまさらだけど、この実行結果分かり辛いな。
「dataObj.data + " ⇔ " + event.data.data」右と左のオブジェクトで、
左の元のオブジェクトは全然書き変わってない。ワーカースレッドには値渡しでオブジェクトを渡すらしい。


別スレッドでガンガンアプリケーションを動かす(?)ってのは考えないほうがいいのかな。
でも、やってみたい。
で、ワーカースレッドのソースをいじる。

addEventListener("message", function(event){
    setTimeout(function(){
        for(var i = 0; i < 1000000; i++){
            new Date();//ここもワーカースレッドで動作。
        };
        postMessage(event.data);
    }
    , 2000);
}, false);

setTimeoutで処理されるfunctionもワーカースレッドで動作。
どうにかして何かに使えないかなぁ。
ちなみにfunctionを渡そうとしたけど無理でした。
何か(?)が渡るけどObject扱いになります。スレッド間で直接の参照は出来ない!




ちなみにテスト3(6スレッド並列処理)をFirefoxで動かした結果。
Firefox3.6(core2 duo 1.83GHz)

Web Workers並行処理のテスト
2:1344msec.
3:1347msec.
5:2230msec.
4:2290msec.
1:2459msec.
6:3215msec.

2スレッドづつ処理してる?


別な環境でもやってみた。
Firefox3.6(core i7 860)

Web Workers並行処理のテスト
2:3779msec.
3:3781msec.
1:3783msec.
5:7662msec.
4:7666msec.
6:7668msec.

3スレッド。
なんか、限界が低いみたいだね。
そしてcore i7の方が遅いのはどうしてか?BIOSの省電力設定のせい?




ChromeはローカルでWeb Workersが動かないらしい。
気づかずに小一時間悩んでしまった。


Chrome8(こいつだけサーバーに上げて実行)

Web Workers並行処理のテスト


1:349msec.
2:535msec.
3:878msec.
4:1089msec.
5:1274msec.
6:1464msec.

よーし動いた、うご……あれ?