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.
よーし動いた、うご……あれ?