SlideShare a Scribd company logo
ブラウザ JavaScr ip t 高速化
JIT バトル最終決戦


12-B-5    森田創
          Co m m u n it y En g in e, In c
          o m o @d o d g so n .o r g
本日の趣旨
J a va S c r ip t VM に使われている
高速化技法を
紹介します。




                               2
自己紹介

森田創
  勤め先 : Co mm unity Eng ine ( 株 )
●


     オンラインゲームなどの開発や開発支援
     オンラインゲームのミドルウェア販売
    仕事 :↑ らのためにコードを書くこと
●


        主に C+ + ( たまに r ub y, p ytho n, Ac t io nS c r ip t...)
    ●


        ウェブとかよくわかんない
    ●



    アマチュア J a va S c r ip t VM 評論家
●


                   ↑ これでよばれた。
                                                                  3
参考:
アマチュア野球評論家(イメージ)↓




                4
疑問が3つ。




         5
疑問 1/3

ほんとに
最終決戦とやらが
ホットなのか?




           6
/speedup|optimiz/ の回数
 @Saf ar i JS エンジンの ChangeLog
  50

  45

  40

  35

  30
                                                                                           Go o g le Chr o me 登場
  25

  20
                                                                                                S a f a r i J S 刷新
  15
S a f a r i リリース                                       “ x”登場
                                                       Aja
  10

   5

   0
       02



                   02



                               03



                                           03



                                                       04



                                                                   05



                                                                               05



                                                                                           06



                                                                                                       06



                                                                                                                   07



                                                                                                                               07



                                                                                                                                           08
                                                                                            /0



                                                                                                        /1




                                                                                                                                            /0
        /0



                    /0



                                /0



                                            /1



                                                        /0



                                                                    /0



                                                                                /0




                                                                                                                    /0



                                                                                                                                /1
                                                           6




                                                                                               3



                                                                                                           0




                                                                                                                                               7
           2



                       9



                                   4



                                               1




                                                                       1



                                                                                   8




                                                                                                                       5



                                                                                                                                   2
                                                                                                /0



                                                                                                            /0




                                                                                                                                                /0
            /0



                        /0



                                    /0



                                                /0



                                                            /0



                                                                        /0



                                                                                    /0




                                                                                                                        /0



                                                                                                                                    /0
                                                                                                                                                       7
               1



                           1



                                       1



                                                   1



                                                               1



                                                                           1



                                                                                       1



                                                                                                   1



                                                                                                               1



                                                                                                                           1



                                                                                                                                       1



                                                                                                                                                   1
疑問 2/3

ほんとに
速くなると
うれしいのか?




          8
私家版手動ベンチマーク
 with oprofile


gmail




                         libmozjs
        libxul
gdocs



    0%           20%   40%       60%   80%   100%

                              システムの10− 20%
                             ●


                             •プロセスの3割     9
疑問 3/3

この話は
皆様にとって
何の役に立つのか?




            10
答え3:蘊蓄の足しになる
蘊蓄を愛でるのは日本人として当然の嗜み




                      11
ブラウザ J a v a Sc r ip t 高速化
J IT バトル最終決戦
観戦ガイド


                  森田創
                  Co m m u n it y En g in e, In c
                  o m o @d o d g so n .o r g
                                              12
これまでのあらすじ
登場人物 VM x3
  V8
●

  f r o m Go o g le Chro m e
  SquirrelFish Extreme
●

  f r o m Ap p le S a f a ri/We b Kit
  TraceM onkey
●

  f r o m Mo zilla Fire f o x

が、血みどろの戦いを繰り広げていた!!!
                                        13
V8 の系譜
    S un から引き抜かれた
●

    La r s Ba k がエース
        S t r o ng ta lk VM
    ●


        J a va Ho t s p o t VM
    ●


    コードにも
●

    S t r ong t a lk の残骸
        GC
    ●


        アセンブラ
    ●




                                 14
SquirrelFish Extreme の系譜

    J a va S c r ip tCo r e 高速化バージョン
●


        バイトコード化 (“ q uirr e lFis h”
    ●
                           S      )
        J IT 追加 (“
    ●
                 Extre m e ”
                           )
    KHTML/KJ S から派生
●


        まだあるよ↑
    ●




                                       15
TraceMonkey の系譜
    S p id e rMo nke y
●

              +
    Ta m a r in Tr a c ing
    And r e a s Ga l の研究成果
●


        コードも書いてる
    ●




                             16
決戦の舞台
    V8
●

    b r a nc he s /b le e d ing _e d g e r1198
    S q uir re lFis h Extre me
●

    t r unk r40447
    Tr a c e Mo nke y
●

    hg .m o zilla .o rg /tr a c e m o nke y 24328

スライド作成時の最新版
※ すべて未リリース
                                                    17
本題 :JavaScript VM の高速化
J a va S c r ip t は遅い。なぜなら・・・
    昔は実装をさぼっていた。
 ●


        うざいポップアップを出せればよかった。
    ●


        g m a il も zo ho もなかった。
    ●




    言語仕様も割と面倒。
●


    でも最近はがんばっている。
●


        最終決戦ですから !
    ●

                      今日のテーマ
                                  18
JSVM 高速化の戦略
    J S 言語固有の面倒をとりのぞき、
●


    既存 VM の高速化技法をとりいれる
●


        J a va , S ma llta lk, Lis p , …
    ●


    ウェブアプリのツボをチューンする
●




                                           19
JSVM 高速化の見所3つ
    J S 言語固有の面倒をとりのぞき、
●


    既存 VM の高速化技法をとりいれる
●


        J a va , S ma llta lk, Lis p , …
    ●


    ウェブアプリのツボをチューンする
●




                                           20
JSVM 高速化の見所3つ+1
    J S 言語固有の面倒をとりのぞき、
●


    既存 VM の高速化技法をとりいれる
●


        J a va , S ma llta lk, Lis p , …
    ●


    ウェブアプリのツボをチューンする
●


    競合 VM への追従
●


        オープンソース+宣伝熱心
    ●

        ∴ノーガードぱくり殴り合い


                                           21
ここからのあらすじ
    J S 言語固有の面倒 :
●

    クラスがないのをどうにかする話
    既存 VM 技術の応用 :
●

    メソッド呼び出しの高速化の話
    ウェブアプリのツボ :
●

    正規表現の高速化の話

    どれも三大 VM すべてが実装している
●

    激戦区。
                          22
ここからのあらすじ
    J S 言語固有の面倒 :
●

    クラスがないのをどうにかする話
    既存 VM 技術の応用 :
●

    メソッド呼び出しの高速化の話ヘ
             ∴ã€


    ウェブアプリ固有のホットスポット :
●

    正規表現の高速化の話




                         23
復習:
クラスがあるとどう嬉しい?

class Point {
public:
   int x; int y;
};

Point *p = new Point();
p->x = 10;
p->y = 20;
                          クラスの例 :C+ +

                                    24
こたえ:
クラスがあれば構造がわかる
    クラス=配列
●
    (C+ + , J a va 処理系にとって。 )

    構造=メンバ変数の並び
●




    メンバ変数アクセスが
●

    高速!


                                25
class Point {
public:
   int x; int y; int z;
};
....
Point *p = new Point();
p->x = 10;   // movl $10,   (%eax)
p->y = 20;   // movl $20,   4(%eax)
....
int *q = new int[3];
             // movl $10,   (%eax)
q[0] = 10;
             // movl $20,   4(%eax)
q[1] = 20;



                                      26
Point @ JavaScript

function Point(x0, y0) {
  this.x = x0;
  this.y = y0;
}
…
var p = new Point(10, 20);




                             27
Point @ JavaScript
function 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 プロパティができる
  }
}


    オブジェクトにクラスがない
●

    =プロパティに制限がない
    =構造が事前に決まらない
                                      28
    配列にはできそうもない・・・
●
JS のオブジェクトはハッシュ表
素朴な




                    ヘ
            ∴ã€




    プロパティアクセス=ハッシュ表検索
●


    ハッシュ表は遅いらしい
●

                        29
ベンチマーク :
配列 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; }
                                                  30
ベンチマーク結果
    hash


    array




            0   5   10   15   20   25   30   35   40



短い方が高速(横軸は実行時間)
    7倍遅い!
●


    クラスがないオブジェクトはハッシュ表
●

    →なんとかしたい。                                          31
洞察:
大半はいつも同じだよね?

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;
  }
}



                                  32
アイデア:
仮のクラスを割り振ってみる
var p = new Point();
p.x = 10;
p.y = 20;




                       ※ クラスが構造を知っている
                                    33
別の構造には別のクラスを・・・




var q = new Fullname();
q.first = “Jose”;
q.middle = “Luis”;
q.last = “Carol”


                          34
同じ構造には同じクラスを・・・
var p1 = new Point();
p1.x = 10;
p1.y = 20;

var p2 = new Point();
p2.x = 10;
p2.y = 20;




                        35
途中で構造が変わったら?

…
var p1 = new Point();
p1.x = 10;
p1.y = 20;

var p2   = new Point();
p2.x =   10;
p2.y =   20;
         30; // 仮クラスの構造にあわない!
p2.z =



                                36
あとから新しいクラスが派生




                37
JSVMs には
仮のクラスを類推するしくみがある
プログラム実行時に
  同じ構造のオブジェクトに同じクラスを
●


  違う構造のオブジェクトに違うクラスを
●


割り振る。→オブジェクトの構造がわかる

    構造が変わるとクラスも変わる。
●


    J S プログラマからクラスはみえない。
●


        高速化のトリック。
    ●
                           38
各 VM 上での名前
    V8 では
●

    Hidden Class
    Tr a c e Mo nke y では
●

    Shape Inf erence
    S q uir re lFis h Extre me では
●

    Structure Chain

    どれもだいたい同じもの。
●


        歴史の長さは TM > V8 > S FX
    ●


                                    39
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 }; }




                                                       40
クラスができた。次は・・・




                41
ここからのあらすじ
    J S 言語固有の面倒 :
●

    クラスがないのをどうにかする話 ( 済 )
    既存 VM 技術の応用 :
●

    メソッド呼び出しの高速化の話ヘ
              ∴ã€


    ウェブアプリ固有のホットスポット :
●

    正規表現の高速化の話




                            42
ここからのあらすじ
    J S 言語固有の面倒 :
●

    クラスがないのをどうにかする話 ( 済 )
    既存 VM 技術の応用 :
●

    メソッド呼び出しコードの高速化の話
                  ヘ
              ∴ã€

      JIT が生成する
    ウェブアプリ固有のホットスポット :
●

    正規表現の高速化の話




                            43
復習 (1/3): JIT て何だっけ?
    高級言語のプログラムを
●

    実行時に機械語へ変換する高速化
        実行時= J us t In Tim e
    ●


    具体的には
●

    J a va S c r ip t VM のバイトコードを
    機械語に変換すること




                                    44
JIT 前(インタプリタ)
                   ここがバイトコード
while (true) {
  switch(instructions[pc++]) {
  case OP_XXX:
    do_xxx(); // ほんとはインライン
    break;
  case OP_YYY:
    do_yyy();
    break;
    ....
  }
}                                45
JIT 後(生成された機械語)
do_xxx();
do_yyy();
do_zzz();
....
            生成される機械語の疑似コード




                        46
JIT の利点と制限
インタプリタのオーバーヘッドがなくなる
● while(true)


    instructions[pc++]
●


    break;
●


個々のバイトコード実装は速くならない。
      例:メソッド呼び出し

  遅い命令を速くする工夫が必要。
●


  実行時にコードを出力できる
●


        実行結果を利用した高速化ができる
    ●


                           47
復習 (2/3):
JS のメソッド呼び出し
function Greeting() {
  this.hello = function() {
     alert(quot;hello!quot;);
  };
}

var g = new Greeting();
g.hello();


                              48
復習 (2/3):
JS のメソッド呼び出し
function Greeting() {
  this.hello = function() {
     alert(quot;hello!quot;);
  };
}
               / / プロパティをとりだして
               / / t h is つきで呼ぶのと同じ。
var g = new Greeting();
g.hello();     var gh = g.hello;
               gh.apply(g);

                                       49
復習 (2/3):
JS のメソッド呼び出し




    ↓ プロパティ取得を速くしたい
      var gh = g.hello;
      gh.apply(g);

                          50
復習 (3/3)
JS は動的型付けの言語
    動的型付けの言語では
●

    プロパティの取得が遅いらしい
    でも
●

    構造がわかれば速いんでしょ?
    仮クラスによって
●

    オブジェクトにはクラスができた




                      51
復習 (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;
}




                               52
クラスがある=構造がわかる
int lengthSquared(Point p)
{
  // pushl %ebp
  // movl %esp, %ebp
  int x = p.x;
  // movl 12(%ebp), %edx ← 構造がわかるから
                           1命令でアクセスできる
  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                                 53
}
復習 (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;
}
        構造の違う二つのコンストラクタがあって・・・
                             54
復習 (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(quot;yakusouquot;, 10, 20);
var itlensq = lengthSquared(it);
                                   55
動的型付けの言語は
変数にクラス指定がない
    オブジェクトごとのクラスはわかっても
●


    変数のクラスは ( 調べるまで ) わからない
        構造を仮定した高速化ができない
    ●


    動的型付け言語一般の問題
●


        J a va S c r ip t だけじゃない。
    ●




                                    56
JS のプロパティアクセスは
素朴な
結局ハッシュ表検索 (+ 配列アクセス)

               // 等価な C++ 風コード
// JS
var x = p.x;   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];
               }                               57
クラスのハッシュ表から
●

    構造を検索して



    検索結果を使い
●

    プロパティ配列にアクセス




                   58
洞察 :
だいたい同じクラスを使うよね?


function lengthSquared(p) { // 長さの二乗
  return p.x*p.x + p.y*p.y;
}

var pt = new Point(10, 20);
var it = new Item(quot;yakusouquot;, 10, 20);

// こっちが大半で
for (var i=0; i<100; i++) {
  var point_len = lengthSquared(pt);
}
// こっちはたまにだよね?
var item_len = lengthSquared(it);       59
アイデア :
よく使うクラスに特化してみる
               // 等価な C++ 風コード
// JS
var x = p.x;   if (p->klass == PointClass) {
                  // クラスを調べて、
                  // よく使うクラスなら速く
                  return Point_GetX((int*)p);
               } else {
                  return p->getProperty(...);
               }
               …
               Object*
               Point_GetX(int* p) { // 生成する
                 return p[1]; // 構造を知っている!
               }
                                           60
コード
           ...


if (p ->k lass = Po in t Class)
                                  p ->Get Pr o p er t y ()
Po in t _Get X ((in t *)p )



          コード
           ...


                                                             61
コード
     ...


  よく使う型か?
                型を仮定しない
型を仮定した速いコード !    遅いコード
                   ...


    コード
     ...


                          62
予想が間違っていたら?
var pt = new Point(10, 20);
var it = new Item(quot;yakusouquot;, 10, 20);

for (var i=0; i<100; ++i) {
  var p0lensq = lengthSquared(pt);
}

// こっちはたまにだよね?こっちがメインだった・・・
for (var i=0; i<100000; ++i) {
  var p1lensq = lengthSquared(it);
  ...
}

                                        63
元のコードを書き換える !
               // 等価な C++ 風コード
// JS
var x = p.x;   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];
               }
                                                64
コード
      ...


  よく使う型 ' か?
  よく使う型か?
                 型を仮定しない
型を仮定した速いコード !!
型 ' を仮定した速コード     遅いコード
                    ...


     コード
      ...


                           65
JSVMs は
投機的な型付きコードを生成する
  ある型専用のコードを生成しておき、
●


  オブジェクトの型をチェックしてから、
●


  そのコードを呼び出す仕組み
●


適用箇所
  プロパティアクセス
●


  そのあとのメソッド呼び出し
●


  四則演算
●




                       66
四則演算の例
         // 等価な C++ 風コード
// JS
a + b;   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);
         }

                                                67
各 VM 上での名前
    V8 , S q uir r e lFis h Ext r e m e では
●

    Po ly m o r p h ic In lin e Cac h in g
        SELF 言語由来
    ●


        J a va Hotspot VM でも利用
    ●


    Tr a c e Mo nke y では
●

    Tr ac e Tr ee
        J a va Hotpath Re s e a r c h VM 由来
    ●


        Tr a c ing 自体は Dynamo VM 由来
    ●


    コアのアイデアはよく似ている
●


    実現方法はちょっと違う                               68
●
Polymorphic Inline Caching
    コードの中 (Inline ) に
●

    比較用のクラスを保存する (Ca c hing )
    一つ以上のクラス (Po lym o r p hic ) を
●

    保存する
    if (p->klass == ItemClass) {
       return Item_GetX((int*)p);
    } else if (p->klass == PointClass) {
       return Point_GetX((int*)p);
    } else {
       return p->getProperty(...);
    }
                                           69
Tracing Tree
    バイトコードを実行しながら (Tr a c ing )
●

    ひとつながりのネイティブコードを生成
        コードがインライン化される
    ●

        ( メソッド呼び出しもインライン化 )
    生成されたコード片は合流しない (Tr e e )
●




    if (p->klass == ItemClass) {
       return [(int*)p][2]; // inlined!
    } else {
       ...
    }
                                          70
// 設定切替
                              ベンチマーク
var mono = false;
var n = 10000;
var arr = [];

for (var i=0; i<n; ++i) {
  if (mono) {
    arr.push(new Point(i, i)); mono
  } else {
                                                poly
    arr.push((i%2) ? new Point(i, i) :
                     new Item(quot;itemquot;, i, i));
  }
}

for (var i=0; i<n; ++i) {
  for (var j=0; j<n; ++j) {
    lengthSquared(arr[j]);
  }                                               71
}
ベンチマーク結果

V8




SFX                                               mono
                                                  poly




TM


      0   10   20   30   40   50   60   70   80

短い方が高速(横軸は実行時間)                                      72
世間並みになった。次は・・・




                 73
ここからのあらすじ
    J S 言語固有の面倒 :
●

    クラスがないのをどうにかする話 ( 済 )
    既存 VM 技術の応用 :
●

    メソッド呼び出しの高速化の話(済)
                  ヘ
              ∴ã€


    ウェブアプリのツボ :
●

    正規表現の高速化の話




                            74
復習 (1/2) :
正規表現ってどう実装するの?
二つのアプローチ
  状態遷移表を作る路線
●


        DFA, NFA, ...
    ●


        g r ep , lex , …
    ●


    インタプリタを作る路線
●

        構文木、バイトコード、…
    ●


        ライブラリではこっちが主流
    ●


        p c r e, 鬼車 , ORO, JDK , …
        JS もこっちが多かった (PCRE 採用など )
    ●




                                     75
復習 (2/2):
インタプリタの高速化といえば?
これまでさんざん話してきましたが ...




                       76
アイデア :
正規表現も JIT したら?
最終決戦だし。

    パース→バイトコード→機械語生成
●




                       77
JSVMs には
正規表現の JIT コンパイラがある
ただし
  すべてが J IT されるわけではない。
●

  がんばり具合は各社各様。
        V8 : regexp2000 ブランチがマージされた
    ●


        S FX: 最初から搭載
    ●


        TM: サポート弱め
    ●


    各プロジェクト絶賛開発中
●




                                      78
ベンチマーク結果
sunspider/regexp-dna.js


   V8(JIT)



 V8(NOJIT)



      SFX



       TM

             0   0.5   1   1.5   2   2.5   3   3.5



短い方が高速(横軸は実行時間)                                      79
3つの見所まとめ
  クラスがない J S の制限を乗り越える
●

  仮クラス割り振りの仕組み
  既存の VM 技術を適用した
●

  投機的な型付きコード生成の仕組み
  ウェブアプリのホットスポット
●

  正規表現の J IT 化
は、3大 VM すべてが実装。でも
  実装のがんばりには差がある。
●




                         80
今日でてこなかった話
    VM っぽい高速化
●


        GC (コピーなんとか、世代なんとか・・・)
    ●


        命令セット(粒度、レジスタ vs スタック)
    ●


        ネイティブコード呼び出し
    ●


    コンパイラっぽい高速化
●


        レジスタ割当
    ●


        共通部分式の除去
    ●


        ・・・
    ●




                             81
今後の見所
    ブラウザを含めたアプリの総合性能
●


        FF1 1 ベンチ → g m a il ベンチ
    ●


    モバイル機器での性能
●


        消費メモリ
    ●


        ARM CPU 向けの高速化
    ●


    Int e rne t Exp lo re r の動向
●


        今は遅すぎて話題にすらならず。
    ●




                                   82
観戦 HOWTO
    とりあえずコードを読んでみよう
●



     コア部分は 1 -2 万行程度
    ●


    元ねたの論文を探して読もう
●


     大抵コードよりわかりやすい
    ●


    プロジェクトのブログを読もう
●


      Mo zilla , We b kit, Chr o m e すべてあり
    ●


      開発者自身のブログもあり
    ●


    開発記録を読もう
●


        ChangeLog 、 M L 、バグトラッカー
    ●


        新機能もバグトラックで管理するのが定石
    ●
                                             83
ブラウザ J a v a Sc r ip t 高速化
J IT バトル最終決戦
観戦ガイド



       ご清聴ありがとうございました。
                             84
写真たち
    htt p ://f ic kr.c o m /p ho to s /d c jo hn/2 4 4 0 1 7 8 8 0 1 /
             l
●



    htt p ://f ic kr.c o m /p ho to s /s ri-h/2 8 6 6 4 5 8 7 4 7 /
             l
●



    htt p ://f ic kr.c o m /p ho to s /7 1 5 0 2 6 4 6 @ N0 0 /2 7 7 6 9 0 1 8 2 2 /
             l
●



    htt p ://f ic kr.c o m /p ho to s /a llis o nje nning s /2 5 0 3 4 1 0 2 1 3 /
             l
●



    htt p ://f ic kr.c o m /p ho to s /g r e e nmys t/1 4 6 8 7 6 0 7 9 1 /
             l
●




                                                                                       85

More Related Content

devsummit2009js

  • 1. ブラウザ JavaScr ip t 高速化 JIT バトル最終決戦 12-B-5 森田創 Co m m u n it y En g in e, In c o m o @d o d g so n .o r g
  • 2. 本日の趣旨 J a va S c r ip t VM に使われている 高速化技法を 紹介します。 2
  • 3. 自己紹介 森田創 勤め先 : Co mm unity Eng ine ( 株 ) ●  オンラインゲームなどの開発や開発支援  オンラインゲームのミドルウェア販売 仕事 :↑ らのためにコードを書くこと ● 主に C+ + ( たまに r ub y, p ytho n, Ac t io nS c r ip t...) ● ウェブとかよくわかんない ● アマチュア J a va S c r ip t VM 評論家 ● ↑ これでよばれた。 3
  • 7. /speedup|optimiz/ の回数 @Saf ar i JS エンジンの ChangeLog 50 45 40 35 30 Go o g le Chr o me 登場 25 20 S a f a r i J S 刷新 15 S a f a r i リリース “ x”登場 Aja 10 5 0 02 02 03 03 04 05 05 06 06 07 07 08 /0 /1 /0 /0 /0 /0 /1 /0 /0 /0 /0 /1 6 3 0 7 2 9 4 1 1 8 5 2 /0 /0 /0 /0 /0 /0 /0 /0 /0 /0 /0 /0 7 1 1 1 1 1 1 1 1 1 1 1 1
  • 9. 私家版手動ベンチマーク with oprofile gmail libmozjs libxul gdocs 0% 20% 40% 60% 80% 100% システムの10− 20% ● •プロセスの3割 9
  • 12. ブラウザ J a v a Sc r ip t 高速化 J IT バトル最終決戦 観戦ガイド 森田創 Co m m u n it y En g in e, In c o m o @d o d g so n .o r g 12
  • 13. これまでのあらすじ 登場人物 VM x3 V8 ● f r o m Go o g le Chro m e SquirrelFish Extreme ● f r o m Ap p le S a f a ri/We b Kit TraceM onkey ● f r o m Mo zilla Fire f o x が、血みどろの戦いを繰り広げていた!!! 13
  • 14. V8 の系譜 S un から引き抜かれた ● La r s Ba k がエース S t r o ng ta lk VM ● J a va Ho t s p o t VM ● コードにも ● S t r ong t a lk の残骸 GC ● アセンブラ ● 14
  • 15. SquirrelFish Extreme の系譜 J a va S c r ip tCo r e 高速化バージョン ● バイトコード化 (“ q uirr e lFis h” ● S ) J IT 追加 (“ ● Extre m e ” ) KHTML/KJ S から派生 ● まだあるよ↑ ● 15
  • 16. TraceMonkey の系譜 S p id e rMo nke y ● + Ta m a r in Tr a c ing And r e a s Ga l の研究成果 ● コードも書いてる ● 16
  • 17. 決戦の舞台 V8 ● b r a nc he s /b le e d ing _e d g e r1198 S q uir re lFis h Extre me ● t r unk r40447 Tr a c e Mo nke y ● hg .m o zilla .o rg /tr a c e m o nke y 24328 スライド作成時の最新版 ※ すべて未リリース 17
  • 18. 本題 :JavaScript VM の高速化 J a va S c r ip t は遅い。なぜなら・・・ 昔は実装をさぼっていた。 ● うざいポップアップを出せればよかった。 ● g m a il も zo ho もなかった。 ● 言語仕様も割と面倒。 ● でも最近はがんばっている。 ● 最終決戦ですから ! ● 今日のテーマ 18
  • 19. JSVM 高速化の戦略 J S 言語固有の面倒をとりのぞき、 ● 既存 VM の高速化技法をとりいれる ● J a va , S ma llta lk, Lis p , … ● ウェブアプリのツボをチューンする ● 19
  • 20. JSVM 高速化の見所3つ J S 言語固有の面倒をとりのぞき、 ● 既存 VM の高速化技法をとりいれる ● J a va , S ma llta lk, Lis p , … ● ウェブアプリのツボをチューンする ● 20
  • 21. JSVM 高速化の見所3つ+1 J S 言語固有の面倒をとりのぞき、 ● 既存 VM の高速化技法をとりいれる ● J a va , S ma llta lk, Lis p , … ● ウェブアプリのツボをチューンする ● 競合 VM への追従 ● オープンソース+宣伝熱心 ● ∴ノーガードぱくり殴り合い 21
  • 22. ここからのあらすじ J S 言語固有の面倒 : ● クラスがないのをどうにかする話 既存 VM 技術の応用 : ● メソッド呼び出しの高速化の話 ウェブアプリのツボ : ● 正規表現の高速化の話 どれも三大 VM すべてが実装している ● 激戦区。 22
  • 23. ここからのあらすじ J S 言語固有の面倒 : ● クラスがないのをどうにかする話 既存 VM 技術の応用 : ● メソッド呼び出しの高速化の話ヘ ∴〠ウェブアプリ固有のホットスポット : ● 正規表現の高速化の話 23
  • 24. 復習: クラスがあるとどう嬉しい? class Point { public: int x; int y; }; Point *p = new Point(); p->x = 10; p->y = 20; クラスの例 :C+ + 24
  • 25. こたえ: クラスがあれば構造がわかる クラス=配列 ● (C+ + , J a va 処理系にとって。 ) 構造=メンバ変数の並び ● メンバ変数アクセスが ● 高速! 25
  • 26. class Point { public: int x; int y; int z; }; .... Point *p = new Point(); p->x = 10; // movl $10, (%eax) p->y = 20; // movl $20, 4(%eax) .... int *q = new int[3]; // movl $10, (%eax) q[0] = 10; // movl $20, 4(%eax) q[1] = 20; 26
  • 27. Point @ JavaScript function Point(x0, y0) { this.x = x0; this.y = y0; } … var p = new Point(10, 20); 27
  • 28. Point @ JavaScript function 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 プロパティができる } } オブジェクトにクラスがない ● =プロパティに制限がない =構造が事前に決まらない 28 配列にはできそうもない・・・ ●
  • 29. JS のオブジェクトはハッシュ表 素朴な ヘ ∴〠プロパティアクセス=ハッシュ表検索 ● ハッシュ表は遅いらしい ● 29
  • 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; } 30
  • 31. ベンチマーク結果 hash array 0 5 10 15 20 25 30 35 40 短い方が高速(横軸は実行時間) 7倍遅い! ● クラスがないオブジェクトはハッシュ表 ● →なんとかしたい。 31
  • 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; } } 32
  • 33. アイデア: 仮のクラスを割り振ってみる var p = new Point(); p.x = 10; p.y = 20; ※ クラスが構造を知っている 33
  • 34. 別の構造には別のクラスを・・・ var q = new Fullname(); q.first = “Jose”; q.middle = “Luis”; q.last = “Carol” 34
  • 35. 同じ構造には同じクラスを・・・ var p1 = new Point(); p1.x = 10; p1.y = 20; var p2 = new Point(); p2.x = 10; p2.y = 20; 35
  • 36. 途中で構造が変わったら? … var p1 = new Point(); p1.x = 10; p1.y = 20; var p2 = new Point(); p2.x = 10; p2.y = 20; 30; // 仮クラスの構造にあわない! p2.z = 36
  • 38. JSVMs には 仮のクラスを類推するしくみがある プログラム実行時に 同じ構造のオブジェクトに同じクラスを ● 違う構造のオブジェクトに違うクラスを ● 割り振る。→オブジェクトの構造がわかる 構造が変わるとクラスも変わる。 ● J S プログラマからクラスはみえない。 ● 高速化のトリック。 ● 38
  • 39. 各 VM 上での名前 V8 では ● Hidden Class Tr a c e Mo nke y では ● Shape Inf erence S q uir re lFis h Extre me では ● Structure Chain どれもだいたい同じもの。 ● 歴史の長さは TM > V8 > S FX ● 39
  • 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 }; } 40
  • 42. ここからのあらすじ J S 言語固有の面倒 : ● クラスがないのをどうにかする話 ( 済 ) 既存 VM 技術の応用 : ● メソッド呼び出しの高速化の話ヘ ∴〠ウェブアプリ固有のホットスポット : ● 正規表現の高速化の話 42
  • 43. ここからのあらすじ J S 言語固有の面倒 : ● クラスがないのをどうにかする話 ( 済 ) 既存 VM 技術の応用 : ● メソッド呼び出しコードの高速化の話 ヘ ∴〠JIT が生成する ウェブアプリ固有のホットスポット : ● 正規表現の高速化の話 43
  • 44. 復習 (1/3): JIT て何だっけ? 高級言語のプログラムを ● 実行時に機械語へ変換する高速化 実行時= J us t In Tim e ● 具体的には ● J a va S c r ip t VM のバイトコードを 機械語に変換すること 44
  • 45. JIT 前(インタプリタ) ここがバイトコード while (true) { switch(instructions[pc++]) { case OP_XXX: do_xxx(); // ほんとはインライン break; case OP_YYY: do_yyy(); break; .... } } 45
  • 46. JIT 後(生成された機械語) do_xxx(); do_yyy(); do_zzz(); .... 生成される機械語の疑似コード 46
  • 47. JIT の利点と制限 インタプリタのオーバーヘッドがなくなる ● while(true) instructions[pc++] ● break; ● 個々のバイトコード実装は速くならない。 例:メソッド呼び出し 遅い命令を速くする工夫が必要。 ● 実行時にコードを出力できる ● 実行結果を利用した高速化ができる ● 47
  • 48. 復習 (2/3): JS のメソッド呼び出し function Greeting() { this.hello = function() { alert(quot;hello!quot;); }; } var g = new Greeting(); g.hello(); 48
  • 49. 復習 (2/3): JS のメソッド呼び出し function Greeting() { this.hello = function() { alert(quot;hello!quot;); }; } / / プロパティをとりだして / / t h is つきで呼ぶのと同じ。 var g = new Greeting(); g.hello(); var gh = g.hello; gh.apply(g); 49
  • 50. 復習 (2/3): JS のメソッド呼び出し ↓ プロパティ取得を速くしたい var gh = g.hello; gh.apply(g); 50
  • 51. 復習 (3/3) JS は動的型付けの言語 動的型付けの言語では ● プロパティの取得が遅いらしい でも ● 構造がわかれば速いんでしょ? 仮クラスによって ● オブジェクトにはクラスができた 51
  • 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; } 52
  • 53. クラスがある=構造がわかる int lengthSquared(Point p) { // pushl %ebp // movl %esp, %ebp int x = p.x; // movl 12(%ebp), %edx ← 構造がわかるから 1命令でアクセスできる 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 53 }
  • 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; } 構造の違う二つのコンストラクタがあって・・・ 54
  • 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(quot;yakusouquot;, 10, 20); var itlensq = lengthSquared(it); 55
  • 56. 動的型付けの言語は 変数にクラス指定がない オブジェクトごとのクラスはわかっても ● 変数のクラスは ( 調べるまで ) わからない 構造を仮定した高速化ができない ● 動的型付け言語一般の問題 ● J a va S c r ip t だけじゃない。 ● 56
  • 57. JS のプロパティアクセスは 素朴な 結局ハッシュ表検索 (+ 配列アクセス) // 等価な C++ 風コード // JS var x = p.x; 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]; } 57
  • 58. クラスのハッシュ表から ● 構造を検索して 検索結果を使い ● プロパティ配列にアクセス 58
  • 59. 洞察 : だいたい同じクラスを使うよね? function lengthSquared(p) { // 長さの二乗 return p.x*p.x + p.y*p.y; } var pt = new Point(10, 20); var it = new Item(quot;yakusouquot;, 10, 20); // こっちが大半で for (var i=0; i<100; i++) { var point_len = lengthSquared(pt); } // こっちはたまにだよね? var item_len = lengthSquared(it); 59
  • 60. アイデア : よく使うクラスに特化してみる // 等価な C++ 風コード // JS var x = p.x; if (p->klass == PointClass) { // クラスを調べて、 // よく使うクラスなら速く return Point_GetX((int*)p); } else { return p->getProperty(...); } … Object* Point_GetX(int* p) { // 生成する return p[1]; // 構造を知っている! } 60
  • 61. コード ... if (p ->k lass = Po in t Class) p ->Get Pr o p er t y () Po in t _Get X ((in t *)p ) コード ... 61
  • 62. コード ... よく使う型か? 型を仮定しない 型を仮定した速いコード ! 遅いコード ... コード ... 62
  • 63. 予想が間違っていたら? var pt = new Point(10, 20); var it = new Item(quot;yakusouquot;, 10, 20); for (var i=0; i<100; ++i) { var p0lensq = lengthSquared(pt); } // こっちはたまにだよね?こっちがメインだった・・・ for (var i=0; i<100000; ++i) { var p1lensq = lengthSquared(it); ... } 63
  • 64. 元のコードを書き換える ! // 等価な C++ 風コード // JS var x = p.x; 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]; } 64
  • 65. コード ... よく使う型 ' か? よく使う型か? 型を仮定しない 型を仮定した速いコード !! 型 ' を仮定した速コード 遅いコード ... コード ... 65
  • 66. JSVMs は 投機的な型付きコードを生成する ある型専用のコードを生成しておき、 ● オブジェクトの型をチェックしてから、 ● そのコードを呼び出す仕組み ● 適用箇所 プロパティアクセス ● そのあとのメソッド呼び出し ● 四則演算 ● 66
  • 67. 四則演算の例 // 等価な C++ 風コード // JS a + b; 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); } 67
  • 68. 各 VM 上での名前 V8 , S q uir r e lFis h Ext r e m e では ● Po ly m o r p h ic In lin e Cac h in g SELF 言語由来 ● J a va Hotspot VM でも利用 ● Tr a c e Mo nke y では ● Tr ac e Tr ee J a va Hotpath Re s e a r c h VM 由来 ● Tr a c ing 自体は Dynamo VM 由来 ● コアのアイデアはよく似ている ● 実現方法はちょっと違う 68 ●
  • 69. Polymorphic Inline Caching コードの中 (Inline ) に ● 比較用のクラスを保存する (Ca c hing ) 一つ以上のクラス (Po lym o r p hic ) を ● 保存する if (p->klass == ItemClass) { return Item_GetX((int*)p); } else if (p->klass == PointClass) { return Point_GetX((int*)p); } else { return p->getProperty(...); } 69
  • 70. Tracing Tree バイトコードを実行しながら (Tr a c ing ) ● ひとつながりのネイティブコードを生成 コードがインライン化される ● ( メソッド呼び出しもインライン化 ) 生成されたコード片は合流しない (Tr e e ) ● if (p->klass == ItemClass) { return [(int*)p][2]; // inlined! } else { ... } 70
  • 71. // 設定切替 ベンチマーク var mono = false; var n = 10000; var arr = []; for (var i=0; i<n; ++i) { if (mono) { arr.push(new Point(i, i)); mono } else { poly arr.push((i%2) ? new Point(i, i) : new Item(quot;itemquot;, i, i)); } } for (var i=0; i<n; ++i) { for (var j=0; j<n; ++j) { lengthSquared(arr[j]); } 71 }
  • 72. ベンチマーク結果 V8 SFX mono poly TM 0 10 20 30 40 50 60 70 80 短い方が高速(横軸は実行時間) 72
  • 74. ここからのあらすじ J S 言語固有の面倒 : ● クラスがないのをどうにかする話 ( 済 ) 既存 VM 技術の応用 : ● メソッド呼び出しの高速化の話(済) ヘ ∴〠ウェブアプリのツボ : ● 正規表現の高速化の話 74
  • 75. 復習 (1/2) : 正規表現ってどう実装するの? 二つのアプローチ 状態遷移表を作る路線 ● DFA, NFA, ... ● g r ep , lex , … ● インタプリタを作る路線 ● 構文木、バイトコード、… ● ライブラリではこっちが主流 ● p c r e, 鬼車 , ORO, JDK , … JS もこっちが多かった (PCRE 採用など ) ● 75
  • 77. アイデア : 正規表現も JIT したら? 最終決戦だし。 パース→バイトコード→機械語生成 ● 77
  • 78. JSVMs には 正規表現の JIT コンパイラがある ただし すべてが J IT されるわけではない。 ● がんばり具合は各社各様。 V8 : regexp2000 ブランチがマージされた ● S FX: 最初から搭載 ● TM: サポート弱め ● 各プロジェクト絶賛開発中 ● 78
  • 79. ベンチマーク結果 sunspider/regexp-dna.js V8(JIT) V8(NOJIT) SFX TM 0 0.5 1 1.5 2 2.5 3 3.5 短い方が高速(横軸は実行時間) 79
  • 80. 3つの見所まとめ クラスがない J S の制限を乗り越える ● 仮クラス割り振りの仕組み 既存の VM 技術を適用した ● 投機的な型付きコード生成の仕組み ウェブアプリのホットスポット ● 正規表現の J IT 化 は、3大 VM すべてが実装。でも 実装のがんばりには差がある。 ● 80
  • 81. 今日でてこなかった話 VM っぽい高速化 ● GC (コピーなんとか、世代なんとか・・・) ● 命令セット(粒度、レジスタ vs スタック) ● ネイティブコード呼び出し ● コンパイラっぽい高速化 ● レジスタ割当 ● 共通部分式の除去 ● ・・・ ● 81
  • 82. 今後の見所 ブラウザを含めたアプリの総合性能 ● FF1 1 ベンチ → g m a il ベンチ ● モバイル機器での性能 ● 消費メモリ ● ARM CPU 向けの高速化 ● Int e rne t Exp lo re r の動向 ● 今は遅すぎて話題にすらならず。 ● 82
  • 83. 観戦 HOWTO とりあえずコードを読んでみよう ● コア部分は 1 -2 万行程度 ● 元ねたの論文を探して読もう ● 大抵コードよりわかりやすい ● プロジェクトのブログを読もう ● Mo zilla , We b kit, Chr o m e すべてあり ● 開発者自身のブログもあり ● 開発記録を読もう ● ChangeLog 、 M L 、バグトラッカー ● 新機能もバグトラックで管理するのが定石 ● 83
  • 84. ブラウザ J a v a Sc r ip t 高速化 J IT バトル最終決戦 観戦ガイド ご清聴ありがとうございました。 84
  • 85. 写真たち htt p ://f ic kr.c o m /p ho to s /d c jo hn/2 4 4 0 1 7 8 8 0 1 / l ● htt p ://f ic kr.c o m /p ho to s /s ri-h/2 8 6 6 4 5 8 7 4 7 / l ● htt p ://f ic kr.c o m /p ho to s /7 1 5 0 2 6 4 6 @ N0 0 /2 7 7 6 9 0 1 8 2 2 / l ● htt p ://f ic kr.c o m /p ho to s /a llis o nje nning s /2 5 0 3 4 1 0 2 1 3 / l ● htt p ://f ic kr.c o m /p ho to s /g r e e nmys t/1 4 6 8 7 6 0 7 9 1 / l ● 85