Thomas Fuchs「Extreme JavaScript Performance」(JSConf.eu2009)
- JS高速化がテーマのプレゼン
- スピーカはscript.aculo.usのクリエータThomas Fuchs
- 2009年11月に開催のJSConf.euより
- 発表時点でJITコンパイラ非搭載なOperaはベンチマーク比較から外れてる(ieは比較対象に入ってる)
- JITコンパイラとEcmascript5の関係、両者の関係から可能になるjs高速化tip、というくだりは面白かった。
- 聴き取り率8割
- jsではコストかかるんだけど、他の言語だと、Perlだと、・・・的な話をボロボロ聞き逃してる。
- 聞き取れた内容増えたら追記
スライド
ベンチマークで使ったテストコード
以下斜め読み聴きかじった内容
aboutこのプレゼン
- パフォーマンス改善についての一般的なtipsといくつか例を交えて紹介
- ブラウザ上でのjavascriptパフォーマンスに絞って話する
- javascript実行できる環境はブラウザに限らない(サーバーサイドJSとか)ので一応断っておく
- 「せっかちな最適化は絶対やらない」
- 自分(=Thomas)のパフォーマンスへのスタンス
- 最適化は、問題が発生したときにやるべき
- 具体的なパフォーマンス上の問題がないのに手を付けるべきじゃない
- 「現状問題になってないけど、将来問題になるかもしれない(でもどこがって言われるも具体的に挙げれない)」という時はパフォーマンスの最適化に手を付けないこと
ブラウザ上でのjavascriptパフォーマンスということで・・
- 各ブラウザのjavascriptエンジンについて簡単に紹介
- Firefox(SpiderMonkey)
- 最近手が入って早くなってきてる
- Safari(JavaScriptCore)
- よい
- IE(JScript)
- Chrome(V8)
- よい。google発。ブラウザベンダーとは独立してやってる。面白い実装。
- Firefox(SpiderMonkey)
TIPS1:function文減らす
関数呼び出しコストを減らして高速化
//ソース例 //悪い function methodCall(){ function square(n){ return n*n }; var i=10000, sum = 0; while(i--‐--‐) sum += square(i); } //良い function inlinedMethod(){ var i=10000, sum = 0; while(i--‐--‐) sum += i*i; }
- 結果
TIPS2:JSの特長押さえて書く
- JavaとかCとか他の言語からjsに入った人は要注意
- jsでも自分が得意な言語と同じようにコードを書かない
- そのコードがjsのパフォーマンスで足を引っ張る、遅いコードである可能性に頭を巡らせるべし。
- jsでも自分が得意な言語と同じようにコードを書かない
- new演算子とparseInt()使ってるコードの最適化を例に
- parseInt()は、tips1と同じで関数の呼び出しコストを減らすという主旨
//new避ける //悪い function classic(){ var a = new Array, o = new Object; } //良い function literals(){ var a = [], o = {}; }
- 結果
//parseInt()使わずに小数点以下切り捨て //before parseInt(12.5); //12 //after ~~(1 * "12.5")//12 //「1 * 文字列」は評価されると小数点つき数字へ変換 //jsでは、ビット演算子による二重否定(~~)によって小数点以下が切り捨てできる。これを活用
- ビット演算子良く知らない?
- MDCのBitwise Operators読め
TIPS3:ループの最適化
- ループを回すときに評価しないといけない式の数を減らしたり
- ループ使わないでやってみたり
//before var test = ''; for (var i = 0;i<10000;i++)//毎回式の評価が3回入る test = test + str; //after var test = '', i = 10000; while(i--) test = test + str;//毎回式の評価は1回だけ
- 結果
- このテストでは速度面の改善ゼロ。だけど式の評価コストは減らせてる
//ループ展開。while/for使わない //before function normalLoop(){ var i=60, j=0; while(i--‐--‐) j++; } //after function unrolledLoop(){ var j=0; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; j++; }
TIPS4:グローバルオブジェクトをキャッシュする
//before function uncached(){ var i = 10000; while(i--) window.test = 'test';//毎々windowオブジェクトを取得してる } //after function cached(){ var w = window, i = 10000;//ループの外でwindowオブジェクトをキャッシュ while(i--) w.test = 'test'; }
閑話休題:ieって常に遅い?
- たいてい遅い
- 優れてる部分もある
- ie正規表現エンジンは他と比べて結構高速
TIPS5:式(expression)を最適化
- 論理学の初歩的知識を活かして式の評価のコストを減らす系
- 式のtrue/falseが確定した時点で式の残りの要素の評価はスキップされる
//アイディア素描 //bofore var b = false, n = 99; function(){ return n*n && b; //n*nとbどちらも評価が必要 } //after var b = false, n = 99; function(){ return b && n*n;//b //bがfalseである時点で自動的に「b && n*n」はfalse。n*nの評価はスキップ。 }
- 結果
var n = 1; if(true && (n=2)) something(); //ケース1 alert(n);//2 if(true || (n=3)) something();//ケース2 alert(n);//2。3にならない。
- 上のようになるわけ。
- 論理式の評価では、前件(左側)の評価で式全体の評価が決まる場合は、後件(右側)の評価はスキップされる。
- ケース1では、「&&」の左右を評価しないと式の評価が決まらないので、nに2がセット
- ケース2では、「||」の左側の評価で、式の評価が決まるため、右側(n=3)の評価はスキップされ、nに3がセットされずnは2のまま
TIPS6:使うのを避けるべき系
//with()避ける //before function(){ var obj = { prop: 'test', str: '' }; with(obj){ var i = 10000; while(i--‐--‐) str += prop; return str; } //after } function(){ var obj = { prop: 'test', str: '' }, i = 10000; while(i--‐--‐) obj.str += obj.prop; return obj.str; }
- 結果
//try〜catch避ける //before var a = 0; function(){ try{ a += 1; } catch(e) {} } //after function(){ a += 1; }
- 結果
- どうしてこういう結果?
おまけ:まれにjavascriptエンジンはソースコードを書き換えてる場合あり
関数のソースコードを取得したい
//普通こうしたい。 //だがクロスブラウザじゃない //firefoxがダメ (function(){...}).toString(); //これを、 (function () { return 2 * 3; }).toString(); //テストするとこうなる function () { return 2 * 3; } //safari。OK function () { return 2 * 3; } //ie。OK function () { return 2 * 3; } //chrome。OK function () { return 6; } //firefox。NG //解決方法。alert文で表示させたいとき alert((function () { return "alert((" + arguments.callee.toString().replace(/\s/g, "") + ")());"; })());
- ブラウザが内部的にjsソースを書き換えるケースが稀にある。
- こういうブラウザの振る舞いはベンチマーク書くときに気にしたほうがいい。
質疑応答より
補足:2009年のJSConf.eu、他のプレゼン
- JSConf.eu on blip.tv
- 2009年JSConf.euの他のスピーカの動画。
- 豪華スピーカ。Ryan Dahl(node.js)、Douglas Crockford、Steve Souders、John Resig、etc..
- Steve Souders「速いはデフォルト」
- Faruk Ates「HTML5/CSS3時代のJS」
- Christophe Porteneuve「Sprockets」
- Railsがりがり。prototype.jsの開発チームの1人。パリジャン
- ウェブアプリで肥大化一方のjsファイル群を管理するRuby製のツールSprocketsの紹介
- スライド
- Francisco Tolmasky「Building Desktop-Caliber Web Apps With Cappuccino and Atlas」
- Robert Nyman「JavaScript - From Birth to Closure」
- Douglas Crockford「JSの現在/未来」
- Remy Sharp「HTML5 JavaScript APIs」
- Malte Ubl「A Meta Object System for JavaScript」
- Alois Reitbauer「Tracing JS Performance」
- Alexander Lang「CouchDB使ってイマドキのウェブアプリ作る」
- Dion Almaer「ウェプアプリの未来」
- 2010年2月に開発中止したgearsの話が多い。
- Ryan Dahl「Node.js」
- Joh Resig「Understanding JavaScript Testing」