2016年6月24日金曜日

WebWorker使用時のメインスレッドとサブスレッドの速度比較

WebWorkerを使うとJavaScriptでもマルチスレッドな処理ができます。
ただし、メインスレッドではdocumentエレメントなどのDOMにアクセスできますが、サブスレッド側からはDOMにはアクセスできません。アクセスしようとするとエラーになります。他にもActiveXObjectなどが使えないなど、サブスレッド側には色々な制約があります。
そのため、サブスレッド側でできることは非常に限られていて、使いどころがあまりないです。。

しかし、ふと思ったのですが「様々な機能をそぎ落として基本的なことしかできないようになっているスレッドは、メインスレッドより高速な処理が可能なのでは?」とデメリットがメリットに転じる可能性を検証したくなりました。



2016/11/02 追記。こちらの記事でメインスレッドの方が37%高速という結果がでました。


検証用ソースは以下
なるべくメインスレッドとサブスレッドの処理内容が等しくなるように、サブスレッド側でも表示更新っぽい処理を行っています。実際にはオブジェクトのプロパティに値を入れているだけですが。
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>web worker</title>
</head>
<body>
<table border=1>
<tr>
<td></td>
<td>メインスレッド</td>
<td>Workerスレッド</td>
</tr>
<tr>
<td>回数</td>
<td></td>
<td></td>
</tr>
<tr>
<td>平均タイム</td>
<td></td>
<td></td>
</tr>
<tr>
<td>最大タイム</td>
<td></td>
<td></td>
</tr>
<tr>
<td>最小タイム</td>
<td></td>
<td></td>
</tr>
</table>
※単位はミリ秒
</body>
<script type="text/jscript">
function getFunctionBody(fun){
return fun.toString().trim().match(/^function[\s\w]*\([\w\s,]*\)\s*{([\w\W]*?)}$/)[1]
}
function fun2Worker(fun){
var str=getFunctionBody(fun), blob=new Blob([str], {type:'text/javascript'}), url=URL.createObjectURL(blob), worker
try{worker = new Worker(url)}
catch(e){
// Workerに渡すアドレスは同一生成元ポリシーを守らなければいけない。
// 変数urlの中身は「brob:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX」のランダムな文字列
// 呼び出し元ファイルのアドレスがローカルのものなら、ローカルPathしか渡せない。
var fs=new ActiveXObject('Scripting.FileSystemObject'), path=fs.getSpecialFolder(2)+'/'+fs.getTempName()+'.js', ws=fs.CreateTextFile(path)
ws.Write(str)
ws.Close()
worker = new Worker(path)
}
return worker
}
カウント=function(){
var 開始=new Date()
for(var i=0,L=40000000; i<L ;i++){}
var 終了=new Date()
return 終了.getTime() - 開始.getTime()
}
表示更新=function(swメイン, 結果){
var 列=swメイン?1:2, arr=arr結果[列], 回数=arr.push(結果)
arr.sum += 結果
obj項目.回数 [列][iT] = 回数
obj項目.平均タイム[列][iT] = Math.round(arr.sum / 回数)
obj項目.最大タイム[列][iT] = Math.max .apply(null, arr)
obj項目.最小タイム[列][iT] = Math.min .apply(null, arr)
}
初期化=function(swワーカースレッド側){
arr結果=[0, [], []]
arr結果[1].sum = arr結果[2].sum = 0
iT='innerText', FI='firstChild', CN='childNodes'
if(!swワーカースレッド側){return}
obj項目={
回数 :[0,{},{}],
平均タイム:[0,{},{}],
最大タイム:[0,{},{}],
最小タイム:[0,{},{}]
}
swメイン = false
}
swメイン = true
リピート=function(){
var 結果
表示更新(swメイン, 結果=カウント())
if(!swメイン){postMessage(結果)}
setTimeout(arguments.callee, 300)
}
objワーカーへ転送する={
カウント:カウント.toString(),
表示更新:表示更新.toString(),
初期化 :初期化 .toString(),
リピート:リピート.toString()
}
function gets(elem, name){return elem.getElementsByTagName(name)}
onload=function(){
初期化()
// 各TRのfirstChild.innerTextの文字列から、各TRのchildNodesにアクセスできるようにしておく。
obj項目 = {}
var trs=gets(document.body, 'tr')
for(var i=0,L=trs.length; i<L ;i++){
tds = gets(trs[i], 'td')
obj項目[tds[0][iT]] = tds
}
worker.postMessage(objワーカーへ転送する)
リピート()
}
worker = fun2Worker(function(){
// ワーカー側スコープ
onmessage = function(e){
objワーカーへ転送された = e.data
var arr='カウント/表示更新/初期化/リピート'.split('/')
for(var i=0,L=arr.length; i<L ;i++){
eval(arr[i]+' = '+objワーカーへ転送された[arr[i]])
}
初期化(true)
onmessage=function(e){}
リピート()
}
})
worker.onmessage = function(e){ 表示更新(false, e.data) }
</script>
</html>





実行結果



なんと、メインスレッドの方が早いらしいです。。
2016/11/01訂正。ほんの少しだけWorkerスレッドの方が最大タイムが短いので、速いようです。

あと最大と最小の振れ幅が大きいのも気になります。。


ちなみに実行直後はこんな感じです。



30~50ぐらいで急激に平均タイムが下がり始めます。



CPU使用率も、プログラムの起動直後はやや高くなるのですが



平均タイムが下がり始めるとCPU使用率も下がり



最終的に、プログラムは動き続けているにも関わらず、CPU使用率は起動前とほとんど同じ状態になります。



沢山繰り返すと最適化される?のでしょうか。。

0 件のコメント:

コメントを投稿