森田創 Community Engine, Inc o m o @d o d g s o n .o r g 12-B-5 ブラウザ ブラウザ JavaScript JavaScript 高速化 高速化 JIT JIT バトル最終決戦 バトル最終決戦
森田創Com m unit y Eng ine, Incom o@d od gson.or g
12-B-5
ブラウザブラウザ JavaScriptJavaScript 高速化高速化JITJIT バトル最終決戦バトル最終決戦
2
本日の趣旨
JavaSc r ip t VM に使われている
高速化技法を
紹介します。
3
自己紹介
森田創● 勤め先 : Community Eng ine ( 株 )
オンラインゲームなどの開発や開発支援
オンラインゲームのミドルウェア販売
● 仕事 :↑ らのためにコードを書くこと● 主に C++ ( たまに ruby, python, Ac t ionSc r ip t...)
● ウェブとかよくわかんない
● アマチュア JavaSc r ip t VM 評論家 ↑ これでよばれた。
4
参考:アマチュア野球評論家(イメージ)↓
5
疑問が3つ。
6
疑問 1/3
ほんとに最終決戦とやらがホットなのか?
7
02/0
2/0
1
02/0
9/0
1
03/0
4/0
1
03/1
1/0
1
04/0
6/0
1
05/0
1/0
1
05/0
8/0
1
06/0
3/0
1
06/1
0/0
1
07/0
5/0
1
07/1
2/0
1
08/0
7/0
1
0
5
10
15
20
25
30
35
40
45
50
Safa r i リリース “Ajax” 登場Safa r i JS 刷新
Goog le Chrome 登場
/speedup|optimiz/の回数@Saf ar i JSエンジンのChangeLog
8
疑問 2/3
ほんとに速くなるとうれしいのか?
9
gmail
gdocs
0% 20% 40% 60% 80% 100%
●システムの10−20%•プロセスの3割
私家版手動ベンチマークwith oprofile
libxul libmozjs
10
疑問 3/3
この話は皆様にとって何の役に立つのか?
11
答え3:蘊蓄の足しになる
蘊蓄を愛でるのは日本人として当然の嗜み
12
ブラウザ J a v a Sc r ip t 高速化J IT バトル最終決戦観戦ガイド
森田創Com m unit y Eng ine, Inc
om o@d od gson.or g
13
これまでのあらすじ
登場人物 VM x3● V8
f rom Goog le Chrome ● SquirrelFish Extreme
f rom App le Sa fa r i/WebKit● TraceMonkey
f rom Mozilla Fire fox
が、血みどろの戦いを繰り広げていた!!!
14
V8の系譜● Sun から引き抜かれた
Lars Bak がエース● Strong ta lk VM● Java Hots pot VM
● コードにもStrong ta lk の残骸● GC● アセンブラ
15
SquirrelFish Extremeの系譜
● JavaSc r ip tCore 高速化バージョン● バイトコード化 (“Squirre lFis h”)
● JIT 追加 (“Extreme”)
● KHTML/KJS から派生● まだあるよ↑
16
TraceMonkeyの系譜● Sp ide rMonkey
+ Tamarin Trac ing
● Andreas Ga l の研究成果 ● コードも書いてる
17
決戦の舞台
● V8b ranches /b leed ing _edg e r1198
● Squirre lFis h Extremetrunk r40447
● TraceMonkeyhg .mozilla .org /tracemonkey 24328
スライド作成時の最新版
※ すべて未リリース
18
本題 :JavaScript VMの高速化JavaSc r ip t は遅い。なぜなら・・・● 昔は実装をさぼっていた。
● うざいポップアップを出せればよかった。● g mailも zoho もなかった。
● 言語仕様も割と面倒。● でも最近はがんばっている。
● 最終決戦ですから !今日のテーマ
19
● JS 言語固有の面倒をとりのぞき、● 既存 VM の高速化技法をとりいれる
● Java , Smallta lk, Lis p , …
● ウェブアプリのツボをチューンする
JSVM高速化の戦略
20
● JS 言語固有の面倒をとりのぞき、● 既存VM の高速化技法をとりいれる
● Java , Smallta lk, Lis p , …
● ウェブアプリのツボをチューンする
JSVM高速化の見所3つ
21
● JS 言語固有の面倒をとりのぞき、● 既存 VM の高速化技法をとりいれる
● Java , Smallta lk, Lis p , …
● ウェブアプリのツボをチューンする● 競合VM への追従
● オープンソース+宣伝熱心∴ノーガードぱくり殴り合い
JSVM高速化の見所3つ+1
22
● JS 言語固有の面倒 :クラスがないのをどうにかする話
● 既存 VM 技術の応用 :メソッド呼び出しの高速化の話
● ウェブアプリのツボ :正規表現の高速化の話
● どれも三大VM すべてが実装している激戦区。
ここからのあらすじ
23
ここからのあらすじ
● JS 言語固有の面倒 :クラスがないのをどうにかする話
● 既存 VM 技術の応用 :メソッド呼び出しの高速化の話
● ウェブアプリ固有のホットスポット :正規表現の高速化の話
∴〠ヘ
24
復習:クラスがあるとどう嬉しい?
class Point {public: int x; int y;};
Point *p = new Point();p->x = 10;p->y = 20;
クラスの例 :C++
25
● クラス=配列(C+ + , Java 処理系にとって。 )
● 構造=メンバ変数の並び
● メンバ変数アクセスが高速!
こたえ:クラスがあれば構造がわかる
26
class Point {public: int x; int y; int z;};....Point *p = new Point();p->x = 10;p->y = 20;
// movl $10, (%eax) // movl $20, 4(%eax)
....int *q = new int[3];q[0] = 10;q[1] = 20;
// movl $10, (%eax) // movl $20, 4(%eax)
27
Point @ JavaScript
function Point(x0, y0) { this.x = x0; this.y = y0;}…var p = new Point(10, 20);
28
Point @ JavaScriptfunction Point(x0, has_y, y_or_z) { this.x = x0; if (has_y) { // 実行時にプロパティが変わる! this.y = y_or_z; // y プロパティができる } else { this.z = y_or_z; // z プロパティができる }}
● オブジェクトにクラスがない=プロパティに制限がない=構造が事前に決まらない
● 配列にはできそうもない・・・
29
素朴なJSのオブジェクトはハッシュ表
● プロパティアクセス=ハッシュ表検索● ハッシュ表は遅いらしい
∴〠ヘ
30
ベンチマーク : 配列 vs.ハッシュ表 in C++int benchmark(bool use_hash) { const int NELEM = 100; if (use_hash) { std::tr1::unordered_map<int, int> obj; for (int i=0; i<NELEM; ++i) { obj[i] = i; } for (int i=0; i<NELEM*NELEM*NELEM; ++i) { int x = obj[i%NELEM]; touch(x); } } else { int obj[NELEM]; for (int i=0; i<NELEM; ++i) { obj[i] = i; } for (int i=0; i<NELEM*NELEM*NELEM; ++i) { int x = obj[i%NELEM]; touch(x); } }}
int touch(int x) { volatile int y = x; }
31
ベンチマーク結果
● 7倍遅い!● クラスがないオブジェクトはハッシュ表
→なんとかしたい。
hash
array
0 5 10 15 20 25 30 35 40
短い方が高速(横軸は実行時間)
32
洞察:大半はいつも同じだよね?
function Point(x0, has_y, y_or_z) { this.x = x0; if (has_y) { // 実行時にプロパティが変わる! this.y = y_or_z; } else { this.z = y_or_z; }}
↑ こんなことしないよね?
33
アイデア:仮のクラスを割り振ってみる
var p = new Point();p.x = 10;p.y = 20;
※ クラスが構造を知っている
34
別の構造には別のクラスを・・・
var q = new Fullname();q.first = “Jose”;q.middle = “Luis”;q.last = “Carol”
35
同じ構造には同じクラスを・・・var p1 = new Point();p1.x = 10;p1.y = 20;
var p2 = new Point();p2.x = 10;p2.y = 20;
36
途中で構造が変わったら?
… var p1 = new Point(); p1.x = 10; p1.y = 20; var p2 = new Point(); p2.x = 10; p2.y = 20; p2.z = 30; // 仮クラスの構造にあわない!
37
あとから新しいクラスが派生
38
JSVMsには仮のクラスを類推するしくみがある
プログラム実行時に● 同じ構造のオブジェクトに同じクラスを● 違う構造のオブジェクトに違うクラスを
割り振る。→オブジェクトの構造がわかる
● 構造が変わるとクラスも変わる。● JS プログラマからクラスはみえない。
● 高速化のトリック。
39
各VM上での名前● V8 では
Hidden Class● TraceMonkey では
Shape Inference● Squirre lFis h Extreme では
Structure Chain
● どれもだいたい同じもの。● 歴史の長さは TM > V8 > SFX
40
VM実装毎のこまかな違い
● コンストラクタは違うけどプロパティが同じときは?
function A(x0, y0) { this.x = x0; this.y = y0; }function B(x0, y0) { this.x = x0; this.y = y0; }
● オブジェクトリテラルの扱いは?
function make_a(x0, y0) { return { x:x0, y:y0 }; }
41
クラスができた。次は・・・
42
ここからのあらすじ
● JS 言語固有の面倒 :クラスがないのをどうにかする話 (済 )
● 既存 VM 技術の応用 :メソッド呼び出しの高速化の話
● ウェブアプリ固有のホットスポット :正規表現の高速化の話
∴〠ヘ
43
ここからのあらすじ
● JS 言語固有の面倒 :クラスがないのをどうにかする話 (済 )
● 既存 VM 技術の応用 :メソッド呼び出しコードの高速化の話
● ウェブアプリ固有のホットスポット :正規表現の高速化の話
∴〠ヘJIT が生成する
44
復習 (1/3): JITて何だっけ?● 高級言語のプログラムを実行時に機械語へ変換する高速化● 実行時= J us t In T ime
● 具体的にはJavaSc r ip t VM のバイトコードを機械語に変換すること
45
JIT前(インタプリタ)
while (true) { switch(instructions[pc++]) { case OP_XXX: do_xxx(); // ほんとはインライン break; case OP_YYY: do_yyy(); break; .... }}
ここがバイトコード
46
JIT後(生成された機械語)
do_xxx();do_yyy();do_zzz();....
生成される機械語の疑似コード
47
JITの利点と制限インタプリタのオーバーヘッドがなくなる
● while(true)
● instructions[pc++]
● break;
個々のバイトコード実装は速くならない。● 遅い命令を速くする工夫が必要。● 実行時にコードを出力できる
● 実行結果を利用した高速化ができる
例:メソッド呼び出し
48
復習 (2/3):JSのメソッド呼び出しfunction Greeting() { this.hello = function() { alert("hello!"); };}
var g = new Greeting();g.hello();
49
復習 (2/3):JSのメソッド呼び出しfunction Greeting() { this.hello = function() { alert("hello!"); };}
var g = new Greeting();g.hello();
/ / プロパティをとりだして/ / t h is つきで呼ぶのと同じ。var gh = g.hello;gh.apply(g);
50
復習 (2/3):JSのメソッド呼び出し
var gh = g.hello;gh.apply(g);
↓ プロパティ取得を速くしたい
51
復習 (3/3)JSは動的型付けの言語● 動的型付けの言語では
プロパティの取得が遅いらしい● でも構造がわかれば速いんでしょ?
● 仮クラスによってオブジェクトにはクラスができた
52
復習 (3/3)の前に・・・C++は静的型付けの言語
int lengthSquared(Point p) { int x = p.x; int y = p.y; int xx = x*x; int yy = y*y; int ret = xx + xx; return ret; }
● 変数にクラスがある。
53
int lengthSquared(Point p) { // pushl %ebp // movl %esp, %ebp int x = p.x; // movl 12(%ebp), %edx int y = p.y; // movl 8(%ebp), %eax // leave int xx = x*x; // imull %edx, %edx int yy = y*y; // imull %eax, %eax int ret = xx + xx; // addl %edx, %eax return ret; // ret}
←構造がわかるから1命令でアクセスできる
クラスがある=構造がわかる
54
復習 (3/3)JSは動的型付けの言語
// x と y がプロパティfunction Point(x0, y0) { this.x = x0; this.y = y0;}
// name と x と y がプロパティfunction Item(name, x0, y0) { this.name = name; this.x = x0; this.y = y0;}
構造の違う二つのコンストラクタがあって・・・
55
復習 (3/3)JSは動的型付けの言語
// p のクラスはなに?function lengthSquared(p) { return p.x*p.x + p.y*p.y;}
var pt = new Point(10, 20);var ptlensq = lengthSquared(pt);
var it = new Item("yakusou", 10, 20);var itlensq = lengthSquared(it);
?
?
56
動的型付けの言語は変数にクラス指定がない● オブジェクトごとのクラスはわかっても
変数のクラスは (調べるまで ) わからない● 構造を仮定した高速化ができない
● 動的型付け言語一般の問題● JavaSc r ip t だけじゃない。
57
素朴なJSのプロパティアクセスは結局ハッシュ表検索 (+配列アクセス)
// JSvar x = p.x;
// 等価な C++ 風コードObject* x = p->getProperty(X_NAME_ID);...Object* Object::getProperty(int id){ Hash* h = this->klass->m_propHash; int index = h->lookup(id); Property* p = this->prop_array; return p[index];}
58
● クラスのハッシュ表から構造を検索して
● 検索結果を使いプロパティ配列にアクセス
59
洞察 :だいたい同じクラスを使うよね?
function lengthSquared(p) { // 長さの二乗 return p.x*p.x + p.y*p.y; } var pt = new Point(10, 20); var it = new Item("yakusou", 10, 20); // こっちが大半で for (var i=0; i<100; i++) { var point_len = lengthSquared(pt); } // こっちはたまにだよね? var item_len = lengthSquared(it);
60
アイデア :よく使うクラスに特化してみる// JSvar x = p.x;
// 等価な C++ 風コードif (p->klass == PointClass) { // クラスを調べて、 // よく使うクラスなら速く return Point_GetX((int*)p);} else { return p->getProperty(...);}…Object*Point_GetX(int* p) { // 生成する return p[1]; // 構造を知っている!}
61
コード...
if (p ->k lass = Po in t Class)
Poin t _Get X ((in t *)p )
p ->Get Pr op er t y ()
コード...
62
コード...
よく使う型か?
型を仮定した速いコード !型を仮定しない遅いコード
...
コード...
63
予想が間違っていたら?
var pt = new Point(10, 20); var it = new Item("yakusou", 10, 20); for (var i=0; i<100; ++i) { var p0lensq = lengthSquared(pt); }
// こっちはたまにだよね?こっちがメインだった・・・ for (var i=0; i<100000; ++i) { var p1lensq = lengthSquared(it); ... }
64
元のコードを書き換える !
// JSvar x = p.x;
// 等価な C++ 風コードif (p->klass == PointClass ItemClass) { return Point_GetX((int*)p); return Item_GetX((int*)p);} else { return p->getProperty(...);}…Object*Item_GetX(int* p) {// 新たに生成 return p[2];}
65
コード...
よく使う型か?
型を仮定した速いコード !型を仮定しない遅いコード
...
コード...
よく使う型 'か?
型 'を仮定した速コード !
66
JSVMsは投機的な型付きコードを生成する● ある型専用のコードを生成しておき、● オブジェクトの型をチェックしてから、● そのコードを呼び出す仕組み
適用箇所● プロパティアクセス● そのあとのメソッド呼び出し● 四則演算
67
四則演算の例
// JSa + b;
// 等価な C++ 風コードif (ClassOf(a) == IntClass && ClassOf(b) == IntClass) { return ((int)a) + ((int)b);} else if (ClassOf(a) == StringClass && ClassOf(b) == StringClass) return ((String*)a)->concat((String*)b);} else { return SlowAdd(a, b);}
68
各VM上での名前● V8 , Squirre lFis h Extreme では
Polym or p h ic In line Cach ing● SELF 言語由来● Java Hotspot VM でも利用
● TraceMonkey ではTr ace Tr ee● Java Hotpath Re s ea rch VM 由来● Trac ing 自体は Dynamo VM 由来
● コアのアイデアはよく似ている● 実現方法はちょっと違う
69
Polymorphic Inline Caching● コードの中 (Inline ) に比較用のクラスを保存する (Caching )
● 一つ以上のクラス (Polymorphic ) を保存する
if (p->klass == ItemClass) { return Item_GetX((int*)p);} else if (p->klass == PointClass) { return Point_GetX((int*)p);} else { return p->getProperty(...);}
70
Tracing Tree● バイトコードを実行しながら (Trac ing )ひとつながりのネイティブコードを生成● コードがインライン化される
( メソッド呼び出しもインライン化 )● 生成されたコード片は合流しない (Tree )
if (p->klass == ItemClass) { return [(int*)p][2]; // inlined! } else { ... }
71
ベンチマーク// 設定切替var mono = false; var n = 10000;var arr = [];
for (var i=0; i<n; ++i) { if (mono) { arr.push(new Point(i, i)); } else { arr.push((i%2) ? new Point(i, i) : new Item("item", i, i)); }}
for (var i=0; i<n; ++i) { for (var j=0; j<n; ++j) { lengthSquared(arr[j]); }}
mono
poly
72
ベンチマーク結果
V8
SFX
TM
0 10 20 30 40 50 60 70 80
monopoly
短い方が高速(横軸は実行時間)
73
世間並みになった。次は・・・
74
ここからのあらすじ
● JS 言語固有の面倒 :クラスがないのをどうにかする話 (済 )
● 既存 VM 技術の応用 :メソッド呼び出しの高速化の話(済)
● ウェブアプリのツボ :正規表現の高速化の話
∴〠ヘ
75
復習 (1/2):正規表現ってどう実装するの?二つのアプローチ● 状態遷移表を作る路線
● DFA, NFA, ...● g r ep , lex , …
● インタプリタを作る路線● 構文木、バイトコード、…● ライブラリではこっちが主流
p cr e , 鬼車 , ORO, JDK, …● JSもこっちが多かった (PCRE 採用など )
76
復習 (2/2):インタプリタの高速化といえば?これまでさんざん話してきましたが ...
77
アイデア :正規表現も JITしたら?最終決戦だし。
● パース→バイトコード→機械語生成
78
JSVMsには正規表現の JITコンパイラがあるただし● すべてが JIT されるわけではない。
がんばり具合は各社各様。● V8 : regexp2000ブランチがマージされた● SFX: 最初から搭載● TM: サポート弱め
● 各プロジェクト絶賛開発中
79
ベンチマーク結果sunspider/regexp-dna.js
V8(JIT)
V8(NOJIT)
SFX
TM
0 0.5 1 1.5 2 2.5 3 3.5
短い方が高速(横軸は実行時間)
80
3つの見所まとめ
● クラスがない JS の制限を乗り越える仮クラス割り振りの仕組み
● 既存の VM 技術を適用した投機的な型付きコード生成の仕組み
● ウェブアプリのホットスポット正規表現の J IT化
は、3大VM すべてが実装。でも● 実装のがんばりには差がある。
81
今日でてこなかった話
● VM っぽい高速化● GC (コピーなんとか、世代なんとか・・・)● 命令セット(粒度、レジスタ vs スタック)● ネイティブコード呼び出し
● コンパイラっぽい高速化● レジスタ割当● 共通部分式の除去● ・・・
82
今後の見所
● ブラウザを含めたアプリの総合性能● FF11 ベンチ → g mailベンチ
● モバイル機器での性能● 消費メモリ● ARM CPU向けの高速化
● Inte rne t Exp lore r の動向● 今は遅すぎて話題にすらならず。
83
観戦HOWTO● とりあえずコードを読んでみよう
● コア部分は 1 -2 万行程度● 元ねたの論文を探して読もう
● 大抵コードよりわかりやすい● プロジェクトのブログを読もう
● Mozilla , Webkit , Chrome すべてあり● 開発者自身のブログもあり
● 開発記録を読もう
● ChangeLog 、 ML 、バグトラッカー● 新機能もバグトラックで管理するのが定石
84
ブラウザ J a v a Sc r ip t 高速化J IT バトル最終決戦観戦ガイド
ご清聴ありがとうございました。
85
写真たち● http ://fl ic kr.com/photos /dc john/2 4 4 0 1 78 80 1 /
● http ://fl ic kr.com/photos /s r i-h/2 8 6 6 4 5 87 4 7 /
● http ://fl ic kr.com/photos /7 1 5 0 2 6 4 6@N0 0 /2 7 7 6 9 0 1 8 2 2 /
● http ://fl ic kr.com/photos /a llis onjenning s /2 5 0 3 4 1 0 2 1 3 /
● http ://fl ic kr.com/photos /g reenmys t/1 4 6 8 7 6 0 7 9 1 /