Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

JSX 速さの秘密 - 高速なJavaScriptを書く方法

24,519 views

Published on

JavaScript で高速なコードを書こうとする際に、はまりがちな罠と、JSX のコンパイラでどのように対処しているのかを紹介

Published in: Technology

JSX 速さの秘密 - 高速なJavaScriptを書く方法

  1. 1. JSX 速さの秘密 〜高速なJavaScriptを書く方法〜 DeNA Co., Ltd. Kazuho Oku 1 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  2. 2. JSXをご存知ですか?  Javaっぽいプログラミング言語です  JavaScriptにコンパイルされます  JavaScriptよりJSXで書いたほうが高速に動作します JSX 速さの秘密 - 高速なJavaScriptを書く方法 2 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  3. 3. Q. 高速な JavaScript を書く方法を教えてください JSX 速さの秘密 - 高速なJavaScriptを書く方法 3 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  4. 4. A. JSX を使いましょう JSX 速さの秘密 - 高速なJavaScriptを書く方法 4 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  5. 5. … JSX 速さの秘密 - 高速なJavaScriptを書く方法 5 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  6. 6. これだけでは芸がないので、JSX が行っている様々な JavaScript 最適化技法を紹介します。 JSX 速さの秘密 - 高速なJavaScriptを書く方法 6 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  7. 7. ※これから説明することは、JSXを使って いれば気にする必要のないことです (自動で最適化されますから) JSX 速さの秘密 - 高速なJavaScriptを書く方法 7 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  8. 8. 目次  高速なJavaScriptを書くための3原則  更なる高速化のために 〜 JSXの最適化コンパイラの仕 事 JSX 速さの秘密 - 高速なJavaScriptを書く方法 8 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  9. 9. 高速なJavaScriptを書くための3原則 JSX 速さの秘密 - 高速なJavaScriptを書く方法 9 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  10. 10. 高速なJavaScriptを書くための3原則  オブジェクトを避ける ⁃ 例: arguments を使わない  Hidden Classを意識したコードを書く ⁃ Inline Cache / 配列の要素の型  組み込み関数が速いとは限らない ⁃ 遅くなることがある • 例: Function#apply, Array#forEach JSX 速さの秘密 - 高速なJavaScriptを書く方法 10 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  11. 11. オブジェクト生成を最小限に  オブジェクト生成を避ける理由 ⁃ メモリ確保に時間がかかる ⁃ GCの原因になる  オブジェクトが生成されるパターン ⁃ new, {...}, [...] ⁃ arguments の使用 • 使った瞬間にオブジェクトが生成されます  オブジェクトが生成されそうで、されないパターン ⁃ ”abc”.foo() • foo 内での this は string? (ES5以降) JSX 速さの秘密 - 高速なJavaScriptを書く方法 11 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  12. 12. Unboxing in JSX  オブジェクトへのアクセスを、複数のローカル変数への アクセスに変換する最適化  最適化前: var pt = new Point(x, y); …  最適化後: var pt$x = x; var pt$y = y; …  注意点: ⁃ オブジェクトを return したり他クラスのオブジェ クトのプロパティにセットしている場合は使えない JSX 速さの秘密 - 高速なJavaScriptを書く方法 12 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  13. 13. プロパティアクセスを最小限に  プロパティアクセス=オブジェクトの要素へのアクセス ⁃ 変数の要素へのアクセスなので、変数へのアクセス より遅い  最適化前: Foo.bar foo.func()  最適化後: Foo$bar // プロパティアクセス → 変数アクセス Foo$func(foo) // メソッド呼出 → 関数呼出 // (呼び出されるメソッドも変換) JSX 速さの秘密 - 高速なJavaScriptを書く方法 13 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  14. 14. newの呼出を避ける  new Type(...) より { ... } の方が高速 ⁃ Safari で有効な最適化。V8 だと遅くなる  最適化前: var pt = new Point(x, y);  最適化後: var pt = { x: x, y: y };  注意点: ⁃ メソッド呼出や instanceof が不可能になる • メソッド呼出については、全頁のメソッドから関数への 変換を適用 JSX 速さの秘密 - 高速なJavaScriptを書く方法 14 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  15. 15. argumentsを使わない  遅いJavaScriptの例: Point.prototype.set = function (a1, a2) { if (arguments.length == 1) { this.x = a1.x; this.y = a1.y; } else { this.x = a1; this.y = a2; } };  高速に書くには、Point型を引数にとる setByPoint メ ソッドと、座標の組を引数にとる setByXY メソッドを 別個に用意すべき JSX 速さの秘密 - 高速なJavaScriptを書く方法 15 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  16. 16. argumentsを使わない – JSXの場合  そもそも arguments がない  関数のオーバーロードが可能 ⁃ コンパイル時に別名になる // コンパイル後の名前: set$Lpoint function set(pt : Point) : void { this.x = pt.x; this.y = pt.y; } // コンパイル後の名前: set$NN function set(x : number, y : number) : void { this.x = x; this.y = y; } JSX 速さの秘密 - 高速なJavaScriptを書く方法 16 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  17. 17. コンストラクタのオーバーロード  通常の関数は名前を変えることでオーバーロード可能  Q. コンストラクタの場合、どうするか?  A. こんなコードを書く (JSXの内部実装より抜粋) function $__jsx_extend(derivations, base) { var ctor = function () {}; ctor.prototype = base.prototype; var proto = new ctor(); for (var i in derivations) derivations[i].prototype = proto; } function Point1(pt) { this.x = pt.x; this.y = pt.y; } function Point2(x, y) { this.x = x; this.y = y; } // new Point1 しても new Point2 しても同じ型のオブジェクトが生成するおまじない $__jsx_extend([ Point1, Point2 ], Object); JSX 速さの秘密 - 高速なJavaScriptを書く方法 17 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  18. 18. Hidden Classを意識したコードを書く http://v8-io12.appspot.com/ JSX 速さの秘密 - 高速なJavaScriptを書く方法 18 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  19. 19. Hidden Classを意識したコードを書く (2)  プロパティのセット順を一意に ⁃ JSXではプロパティ初期化時は条件分岐不可能 • → セット順が一意になる • Java と同様  配列の要素型を一定に ⁃ JSXでは要素型毎に配列型を用意 • number[], string[], Object[], Point[], …  配列の要素をdeleteしない ⁃ JSXでは禁止 • nullを代入することで対処(こちらのほうが高速) JSX 速さの秘密 - 高速なJavaScriptを書く方法 19 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  20. 20. Hidden Classを意識したコードを書く (3)  未初期化の要素にアクセスしない ⁃ JSXでは未初期化の要素にアクセスするとエラー • 注: デバッグビルドのみ ⁃ リリースビルドではエラーチェック省略  Polymorphicなコードを書かない ⁃ JSXではpolymorphicなコードは書けない • クラスベースの型指定が「必須」な処理系だから • テンプレートを使った場合は、引数の型ごとに個別にコ ード生成 JSX 速さの秘密 - 高速なJavaScriptを書く方法 20 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  21. 21. 組み込み関数が速いとは限らない  JavaScript VMの仕事 ⁃ JavaScriptを機械語にJust-In-Time compile • 使われるテクニック: ⁃ Inline Caching ⁃ インライン展開  JavaScriptからC++コードを呼ぶのは遅い(逆も同様) ⁃ C++側で様々なチェックが必要 • 引数の数や型の確認等 • 言語をまたいだInline Cachingやインライン展開は無理 ⁃ C++コードは静的にコンパイルされているため JSX 速さの秘密 - 高速なJavaScriptを書く方法 21 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  22. 22. 組み込み関数が速いとは限らない (2)  汎用的なAPIとして定義されているため遅いケースも ⁃ 数十倍遅いケースも  避けるべき組み込み関数の代表例: ⁃ Function.prototype.call ⁃ Function.prototype.apply ⁃ Function.prototype.bind ⁃ Array.prototype.forEach  ベンチマーク: ⁃ http://jsperf.com/f-p-bind-vs-closure ⁃ http://d.hatena.ne.jp/kazuhooku/20120612/133948 9758 JSX 速さの秘密 - 高速なJavaScriptを書く方法 22 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  23. 23. 組み込み関数が速いとは限らない (3)  JSXの場合: ⁃ Function.prototype 系は存在しない ⁃ クロージャがthisを引き継げるような言語仕様 • → bindの必要性が低い • JSXのソースコード: var f = function () : void { this.n++; }; • コンパイル結果 (JavaScript): var $this = this; var f = function (){ $this.n++; }; JSX 速さの秘密 - 高速なJavaScriptを書く方法 23 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  24. 24. 組み込み関数が速いとは限らない (4)  JSXの場合: ⁃ Array#forEachは独自に実装 ⁃ 言語仕様が硬い分、JavaScriptよりも処理が単純に ⁃ 参照: http://d.hatena.ne.jp/kazuhooku/20120612/133948 9758 JSX 速さの秘密 - 高速なJavaScriptを書く方法 24 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  25. 25. 更なる高速化のために 〜 JSXの最適化コンパイラの仕事 JSX 速さの秘密 - 高速なJavaScriptを書く方法 25 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  26. 26. インライン展開  JavaScript VMは、インライン展開を行う ⁃ だが、最速ではない • 理由: 毎回、呼び出される関数が差し替えられていない か確認する必要があるため ⁃ JSXの場合: • コンパイル後に関数の差し替え不可能 • 最適化コンパイル時に、あらかじめインライン展開され たJavaScriptを生成 ⁃ → JavaScript VMによるインライン展開より高速 ⁃ → 他の最適化も適用可能に JSX 速さの秘密 - 高速なJavaScriptを書く方法 26 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  27. 27. 事前計算  定数畳み込み ⁃ JSXのソースコード: const name = ”John”; … console.log(”Hello, ” + N); ⁃ コンパイル結果 (JavaScript): console.log(”Hello, John”); JSX 速さの秘密 - 高速なJavaScriptを書く方法 27 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  28. 28. 事前計算 (その2)  Dead-code Elimination ⁃ JSXのソースコード: const DEBUG = 0; … if (DEBUG) console.log(”in debug mode”); ⁃ コンパイル結果 (JavaScript): // からっぽ JSX 速さの秘密 - 高速なJavaScriptを書く方法 28 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  29. 29. LTO (リンク時最適化)  以下のコードで、mの型は何? function transform(m: Matrix, pt : Point) : Point { return m.rotate(pt); }  mの型はMatrix型かもしれないし、Matrixの派生型かも ⁃ これでは、rotateの実装を特定できない • → rotateをインライン展開できない  そこでLTO! ⁃ LTO: プログラムが使用する全てのコードに関する情 報を使って(つまり、リンク時に)最適化 ⁃ Matrixを継承した型がなければ、mの型はMatrix型 JSX 速さの秘密 - 高速なJavaScriptを書く方法 29 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  30. 30. アフィン変換 (クラス定義) class Matrix { var m11 : number; var m21 : number; var m31 : number; var m12 : number; var m22 : number; var m32 : number; ... function transform(pt : Point): Point { return new Point( this.m11 * pt.x + this.m21 * pt.y + this.m31, this.m12 * pt.x + this.m22 * pt.y + this.m32); } } class Point { var x : number; var y : number; function constructor(x : number, y : number) { this.x = x; this.y = y; } ... JSX 速さの秘密 - 高速なJavaScriptを書く方法 30 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  31. 31. アフィン変換 (コンパイル例)  JSXのソースコード: var pt = new Matrix(1, 0, 0, 0, 2, 0).transform(new Point(x, y)); x = pt.x; y = pt.y;  最適化コンパイル後 (JavaScript): var pt$x = x + 0 * y; y = 0 * x + 2 * y; x = pt$x;  適用された最適化手法: ⁃ LTO (transformの実装を確定) ⁃ インライン展開 ⁃ Unboxing ⁃ 定数畳み込みとDCE JSX 速さの秘密 - 高速なJavaScriptを書く方法 31 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  32. 32. まとめ JSX 速さの秘密 - 高速なJavaScriptを書く方法 32 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  33. 33. 高速なJavaScriptを書ける自信はありますか?  JavaScriptには速度が遅くなる罠が色々 ⁃ 速くできるところが速くなった結果、罠に落ちた時 の速度の落ち込みが大きくなった  モジュールをまたぐ最適化で高速になるケースも ⁃ 例: アフィン変換  高速かつメンテナンスが容易なJavaScriptコードを書く のは難しすぎる ⁃ グループ開発では教育コストが大きくなりすぎる ⁃ 問題が顕在化してから対処するのでは間に合わない JSX 速さの秘密 - 高速なJavaScriptを書く方法 33 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.
  34. 34. JSXを使えば問題解決するよ!!! JSX 速さの秘密 - 高速なJavaScriptを書く方法 34 Copyright (C) 2013 DeNA Co.,Ltd. All Rights Reserved.

×