JSを事前コンパイルするPrepackを試してみた

  • 27
    いいね
  • 0
    コメント

ゴールデンウィークの最中に、Facebookから「Prepack」が公開されました。

PrepackはJavaScriptのソースコードを最適化するツール。計算可能なことはコンパイル時にしてしまうことで、実行時の性能向上ができます。

FacebookのPrepack

公式サイトのコード

適用前のコードがこれだとしたら

(function () {
  function hello() { return 'hello'; }
  function world() { return 'world'; }
  global.s = hello() + ' ' + world();
})();

Prepackを適用するとこんな感じになります。

(function () {
  s = "hello world";
})();

結果が計算できるようなら、事前に計算されます。例えば、文字列の結合や数値計算など。
これによって、コードの実行速度の向上が期待できます。

使い方

ちょっと試すだけなら公式サイトの「TRY IT OUT」を利用するといいでしょう。

PrepackのTry it out

ローカルに環境を構築するには

手元で使いたい場合は、Node.jsをインストールしたうえで、次の手順でセットアップします。利用するときは、コマンドライン(Windowsではコマンドプロンプト、macOSではターミナル)を使います。

インストール方法

npmでのインストール用コマンド

npm install prepack --save-dev

Yarnでのインストール用コマンド

yarn add prepack -D

実行方法

コマンドで指定

prepack ソースファイル --out 出力ファイル

ソースマップなどさまざまなオプションがあるのですが、詳しくは公式ドキュメント「Prepack · Partial evaluator for JavaScript」を見れば全部わかります。

どれだけ高速化できるか試してみた

検証デモのURLはこちら

スクリーンショット 2017-05-08 10.54.45.png

ソースコード一式はこちら

GitHub : test-prepack-particle

Prepackを通してコードがどれだけ最適化されたか?

最適化前のJSコード

https://github.com/ics-ikeda/test-prepack-particle/blob/master/src/particle.js

(function () {
  const MAX_NUM = 10000; // パーティクルの個数
  const STAGE_W = 465;
  const STAGE_H = 465;
  const FRICTION = 0.96;
  const ACC_VALUE = 50;

  /**
   * パーティクルクラスです。
   * @param {number} x
   * @param {number} y
   * @constructor
   */
  function Particle(x, y) {
    this.x = x;
    this.y = y;
    this.vx = 0;
    this.vy = 0;
  }


  function World() {
    const particleList = [];

    this.init = function () {
      // パーティクルの初期化
      for (var i = 0; i < MAX_NUM; i++) {
        const p = new Particle(
          Math.random() * STAGE_W,
          Math.random() * STAGE_H
        );
        particleList.push(p);
      }
    };

    this.update = function (gravityX, gravityY) {

      particleList.map(function (n) {
        const diffX = gravityX - n.x;
        const diffY = gravityY - n.y;
        const acc = ACC_VALUE / (diffX * diffX + diffY * diffY);
        const accX = acc * diffX;
        const accY = acc * diffY;

        n.vx += accX;
        n.vy += accY;
        n.x += n.vx;
        n.y += n.vy;

        n.vx *= FRICTION;
        n.vy *= FRICTION;

        if (n.x > STAGE_W)
          n.x = 0;
        else if (n.x < 0)
          n.x = STAGE_W;
        if (n.y > STAGE_H)
          n.y = 0;
        else if (n.y < 0)
          n.y = STAGE_H;
      });

      return particleList;
    };
  }

  global.World = World;

})();


最適化後のJSコード

https://github.com/ics-ikeda/test-prepack-particle/blob/master/www/particle.js

(function () {
  var _$0 = this;

  function _0() {
    const particleList = [];

    this.init = function () {
      // パーティクルの初期化
      for (var i = 0; i < 10000; i++) {
        const p = new _1(_$0.Math.random() * 465, _$0.Math.random() * 465);
        particleList.push(p);
      }
    };

    this.update = function (gravityX, gravityY) {
      particleList.map(function (n) {
        const diffX = gravityX - n.x;
        const diffY = gravityY - n.y;
        const acc = 50 / (diffX * diffX + diffY * diffY);
        const accX = acc * diffX;
        const accY = acc * diffY;
        n.vx += accX;
        n.vy += accY;
        n.x += n.vx;
        n.y += n.vy;
        n.vx *= 0.96;
        n.vy *= 0.96;
        if (n.x > 465) n.x = 0;else if (n.x < 0) n.x = 465;
        if (n.y > 465) n.y = 0;else if (n.y < 0) n.y = 465;
      });
      return particleList;
    };
  }

  function _1(x, y) {
    this.x = x;
    this.y = y;
    this.vx = 0;
    this.vy = 0;
  }

  World = _0;
}).call(this);

定数として宣言した数値が、Prepackを通すことでハードコーディングされた形になっています。

このサンプルだと実行速度の向上はあまりわかりませんね…w 計算結果をハードコーディングすることはFlashの時代から最適化の一つの手法として知られていました(可読性が低くなるので、限界に挑戦するときだけ採用してました)。もっと複雑なコードだと恩恵が受けれるかもしれません。

Prepackの所感

公式サイトに「Prepack is still in an early development stage and not ready for production use just yet」(Prepackはまだ初期段階なので、プロダクションに使う状態にはなってないよ)と紹介されているとおり、実務で利用できる段階にはなっていません。

例えば、documentwindowオブジェクトへアクセスするだけでエラーになったり(__assumeDataProperty()メソッドで定義すれば通過できる)、letconstはES5へトランスパイラされたりしません。ES6のアロー関数やclassはエラーになるので、BabelでES5にトランスパイラしてからPrepackを適用するのが良さそうです。

他にも気になることがあったので、Tweetで紹介しておきます。

Closure Compiler

類似の技術としてClosure Compilerがあります。Closure Compilerは容量の最適化に重きを置いていて、Prepackは実行速度に重きを置いています。

まとめ

JavaScriptのパフォーマンス厨としては、将来性が気になるプロジェクトです。現時点では初期段階なので、長い目で進化を待つのがいいでしょう。