以下斜め読んだ内容

pseudo translation of useful posts, book reviews, remarks,etc. twitter: feeddict

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だと、・・・的な話をボロボロ聞き逃してる。
    • 聞き取れた内容増えたら追記

スライド

ベンチマークで使ったテストコード

gist: 227048 - GitHub

以下斜め読み聴きかじった内容

aboutこのプレゼン
  • パフォーマンス改善についての一般的なtipsといくつか例を交えて紹介
  • ブラウザ上でのjavascriptパフォーマンスに絞って話する
    • javascript実行できる環境はブラウザに限らない(サーバーサイドJSとか)ので一応断っておく
  • 「せっかちな最適化は絶対やらない」
    • 自分(=Thomas)のパフォーマンスへのスタンス
    • 最適化は、問題が発生したときにやるべき
    • 具体的なパフォーマンス上の問題がないのに手を付けるべきじゃない
    • 「現状問題になってないけど、将来問題になるかもしれない(でもどこがって言われるも具体的に挙げれない)」という時はパフォーマンスの最適化に手を付けないこと
ブラウザ上でのjavascriptパフォーマンスということで・・
  • 各ブラウザのjavascriptエンジンについて簡単に紹介
    • Firefox(SpiderMonkey)
      • 最近手が入って早くなってきてる
    • Safari(JavaScriptCore)
      • よい
    • IE(JScript)
      • うーん、リッチ?。動いてる。IEの中の人は次世代JSエンジン作ってるだろうから22世紀には公開されんんじゃね?
        • (補足)IE9のJSエンジンChakraについて情報が出回る前のプレゼン
    • Chrome(V8)
      • よい。google発。ブラウザベンダーとは独立してやってる。面白い実装。
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; 
}

  • 結果
    • 0.410s --> 0.150s //Fx
    • 0.056s --> 0.045s //Safari
    • ビジー表示 --> 0.128s //IE
    • 0.027s --> 0.016s /Chrome
    • IE8はテストページ実行して1秒後にビジー表示でる。

TIPS2:JSの特長押さえて書く
  • JavaとかCとか他の言語からjsに入った人は要注意
    • jsでも自分が得意な言語と同じようにコードを書かない
      • そのコードがjsのパフォーマンスで足を引っ張る、遅いコードである可能性に頭を巡らせるべし。
  • new演算子とparseInt()使ってるコードの最適化を例に
    • parseInt()は、tips1と同じで関数の呼び出しコストを減らすという主旨
//new避ける

//悪い
function classic(){ 
var a = new Array, o = new Object; 
}
//良い
function literals(){ 
var a = [], o = {}; 
}

  • 結果
    • 0.291s --> 0.265s //Fx
    • 0.020s --> 0.016s //Safari
    • 0.220s --> 0.185s //IE
    • 0.024s --> 0.010s /Chrome
//parseInt()使わずに小数点以下切り捨て
//before
parseInt(12.5); //12
//after
~~(1 * "12.5")//12
//「1 * 文字列」は評価されると小数点つき数字へ変換
//jsでは、ビット演算子による二重否定(~~)によって小数点以下が切り捨てできる。これを活用

  • 結果
    • 0.003s --> 0.002s //Fx
    • 0.088s --> 0.081s //Safari
    • ビジー表示 --> 0.547s //IE
    • 0.109s --> 0.282s //Chrome
  • Chromeは逆に遅くなる。
    • なぜかはわからない。parseInt()にV8が最適化されてるから?
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回だけ

  • 結果
    • 0.12s --> 0.12s //firefox
    • 0.13s --> 0.13s //safari
    • 0.6s --> 0.6s //ie
    • 0.04s --> 0.04s //chrome
  • このテストでは速度面の改善ゼロ。だけど式の評価コストは減らせてる
//ループ展開。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++; 
}

  • 結果
    • 0.023s --> 0.010s //firefox
    • 0.003s --> 0.001s //safari
    • 0.032s --> 0.015s /ie
    • 0.005s --> 0.001s /chrome
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';
}

  • 結果
    • 1.440s --> 0.825s //firefox
    • 0.07s --> 0.07s //safari
    • 2.22s 2.19s //ie。処理時間1秒超えてるが・・・
    • 0.48s --> 0.16s /chrome
    • IE遅い
      • 挙動不審。2秒以上かかってるけどビジー表示が出ない。
      • tips1、tips2のテストでは1秒超えると即ビジー表示出てたけど、原因わからない。
閑話休題: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の評価はスキップ。
}

  • 結果
    • 0.005s--> 0.004s //firefox
    • 0.011s--> 0.010s //safari
    • 0.906s--> 0.391s //ie
    • 0.037s--> 0.021s/chrome
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:使うのを避けるべき系
  • JITコンパイラの実装に合わせた最適化
  • with()、try〜catch文使わない
  • ieではこのtip使っても使わなくても影響ゼロ
    • ie以外で高速化するのはie以外で実装済のJITコンパイラがwith()、try〜catch文をサポートしてないため
//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; 
}

  • 結果
    • 0.071s --> 0.012s //firefox
    • 0.039s --> 0.028s //safari
    • 0.078s --> 0.078s //ie 変化なし
    • 0.077s --> 0.006s //chrome
//try〜catch避ける

//before
var a = 0; 
function(){ 
try{ 
a += 1; 
} catch(e) {} 
}

//after
function(){ 
a += 1; 
}

  • 結果
    • 0.006s --> 0.005s //firefox
    • 0.287s --> 0.011s //safari
    • 0.460s --> 0.460s //ie 変化なし
    • 0.123s --> 0.012s //chrome
  • どうしてこういう結果?
おまけ:まれに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ソースを書き換えるケースが稀にある。
  • こういうブラウザの振る舞いはベンチマーク書くときに気にしたほうがいい。
質疑応答より
  • ベンチマークの測定方法
    • jsで測ってるけど、DateオブジェクトとgetTime()使ってるだけ
    • このプレゼンで使ってるベンチマーク用の関数は1つだけと、自分が書いた本だとベンチマーク用の関数は複数紹介してる。2回目の呼び出し時間を調べるとか、ガベージコレクション管理の活用とかも分かるやつ書いた。
  • パフォーマンス最適化するときに、なんかソフト使ってる?
    • 使ってない。google clojureの話とかいろいろいってたけど、聴き取れず
補足: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