JSX はなぜ「速い」のか

なぜ「速い」のか、について JSX 開発者の立場から。

たとえば、シューティングゲームで一番重たい処理は何か。言うまでもなく衝突判定。多数の弾や敵機の衝突判定を毎フレームごとに行う必要があり、この演算が重たい。

JSX に同梱されている web/example/shooting.jsx には衝突判定のコードが複数あるが、一番重たいのは Bullet#update 関数で、その処理は以下のようになっている*1

for (var rockKey in st.rocks) {
    var rock = st.rocks[rockKey];
    if (this.detectCollision(rock)) {
        if (rock.hp == 0) return false;
        inDisplay = false;
        if (--rock.hp == 0) {
            st.score = Math.min(st.score + rock.score, 999999999);
            st.updateScore();
            rock.dx = rock.dy = 0;
            rock.setState(st, "bomb1");
        } else {
            var newState = (rock.state +  "w").substring(0, 6);
            rock.setState(st, newState);
        }
    }
}

ループの中で Sprite#detectCollision, Math.min, Stage#udpateScore, Rock#setState などの関数が呼び出されている。これを JSX の --release オプションをつけてコンパイルした結果を見ると、

for (rockKey in st.rocks) {
    rock = st.rocks[rockKey];
    if ((($math_abs_t = this.x - rock.x) >= 0 ? $math_abs_t : -$math_abs_t) < 16 && (($math_abs_t = this.y - rock.y) >= 0 ? $math_abs_t : -$math_abs_t) < 16) {
    if (rock.hp === 0) {
                return false;
        }
        inDisplay = false;
        if (-- rock.hp === 0) {
            st.score = Math.min(st.score + rock.score, 999999999);
            st.updateScore$();
            rock.dx = rock.dy = 0;
            rock.state = "bomb1";
            rock.image = st.images["bomb1"];
        } else {
            newState = (rock.state + "w").substring(0, 6);
            rock.state = newState;
            rock.image = st.images[newState];
        }
    }
}

のように Sprite#detectCollision と Stage#updateScore 関数がインライン展開されていることがわかる (Sprite#detectCollision 関数が呼び出している Math.abs もインライン展開されている)*2

関数がインライン展開されれば、呼び出しにかかっていたオーバーヘッドが消滅するので速度が向上するわけ。

この他にも、定数の畳み込みやオブジェクトのルックアップを減らすなどの最適化を行うことで、速度の向上を図っている。

JavaScript で開発している場合、これらの最適化は手書きで行う必要があるが、コードの保守性とのトレードオフが発生する。

たとえば、上のコンパイル結果に現れた「16」という値。これは、シューティングゲームにおける「機体のサイズ/2」(Config.cellWidth >> 1)という演算を定数として展開した結果だ。この種の定数をソースコードの全ての個所に手書きするのは手間だし、そんなことをしてしまうとプログラムが現実的には保守不可能になっていまう。

JSXは、このような「人間が行うのは現実的には不可能な最適化」を自動的に行うことで、「JavaScriptよりも速い」プログラミング言語になっているのだ*3

そして、この手の大域最適化が適用しやすい、かつ多くの人が馴染んでいるパラダイムは静的型付けを採用したクラスベースのオブジェクト指向言語だよねってことで JSX はそうなっているんだけど、それはまた別の話。

*1:ちなみにこれ、JavaScript のコードに見えますが JSX のソースです。JavaScriptからJSxへの移行の障壁が低いことがわかっていただけると思います

*2:できるだけ JSX ソースコードとしての表現を残してコンパイルしているので、変換後の JavaScript を使ってデバッグすることも容易だろう。Google Chrome ならば JSX のソースコードのままデバッグできるんだけどね

*3:最適化は、まだまだ発展途上だけど。プラス、JavaScript への変換によるオーバーヘッドが発生しない言語設計になっている点も重要