Help us understand the problem. What is going on with this article?

JavaScriptを高速化するFacebook製Prepackを試してみた

More than 1 year has passed since last update.

ゴールデンウィークの最中に、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
   */
  class Particle {
    constructor(x, y) {
      this.x = x;
      this.y = y;
      this.vx = 0;
      this.vy = 0;
    }
  }

  function updateParticlePosition(particle, gravityX, gravityY) {
    const diffX = gravityX - particle.x;
    const diffY = gravityY - particle.y;
    const acc = ACC_VALUE / (diffX * diffX + diffY * diffY);
    const accX = acc * diffX;
    const accY = acc * diffY;

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

    particle.vx *= FRICTION;
    particle.vy *= FRICTION;

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

  class World {

    constructor() {
      this.particleList = [];
    }

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

    update(gravityX, gravityY) {
      this.particleList.forEach((particle) => {
        updateParticlePosition(particle, gravityX, gravityY);
      });

      return this.particleList;
    };
  }

  global.World = World;

})();

最適化後のJSコード

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

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

  var _A = function (particle, gravityX, gravityY) {
    const diffX = gravityX - particle.x;
    const diffY = gravityY - particle.y;
    const acc = 50 / (diffX * diffX + diffY * diffY);
    const accX = acc * diffX;
    const accY = acc * diffY;
    particle.vx += accX;
    particle.vy += accY;
    particle.x += particle.vx;
    particle.y += particle.vy;
    particle.vx *= 0.96;
    particle.vy *= 0.96;
    if (particle.x > 465) particle.x = 0;else if (particle.x < 0) particle.x = 465;
    if (particle.y > 465) particle.y = 0;else if (particle.y < 0) particle.y = 465;
  };

  var _1 = class {
    constructor() {
      this.particleList = [];
    }

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

    update(gravityX, gravityY) {
      this.particleList.forEach(particle => {
        _A(particle, gravityX, gravityY);
      });
      return this.particleList;
    }

  };

  var _8 = class {
    constructor(x, y) {
      this.x = x;
      this.y = y;
      this.vx = 0;
      this.vy = 0;
    }

  };

  _$0.World = _1;
}).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を適用するのが良さそうです。

※Prepackも0.2.6から0.2.47に更新すると、classが利用可能になってました

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

Closure Compiler

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

ライセンス

Facebook製ということもあり、Prepackは「特許条項付きBSDライセンス」という恐ろしいライセンスになっています。かつてReactが採用していたライセンスでバッシングされてましたね。

Prepack is BSD-licensed. We also provide an additional patent grant.

詳しくは記事「Facebookの特許条項付きBSDライセンスが炎上している件について | こんぴゅ | note」が参考になるでしょう。

まとめ

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

clockmaker
ウェブ制作会社ICSの代表および ics.media 編集長をやっています。得意分野はプログラミングアート、インタラクティブ表現の制作。詳しくはリンク先を御覧ください。 https://ics.media/entry/author/ikeda
http://clockmaker.jp/labs/
ics
インタラクションデザイン専門のプロダクション。最先端のウェブテクノロジーを駆使し、オンスクリーンメディアの表現分野で活動しています。最新のウェブ技術を発信するサイト「ICS MEDIA」を運営。
https://ics.media/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした