はじめに

JSX 速いという話を聞いたので、zlib.js を移植したらどうなるのか興味があったので試しました。

お詫び

JSXがリリースされた直後に少し触ってみたのですが、最初期のバージョンでは素のJSとの連携が取りにくかったので、自分の用途には少しマッチしないなと敬遠気味でした。
その認識は最近まで続いていたのですが、現在は export なども出来るようになっているのでライブラリなんかも簡単に作れるようになってます。
(おそらく実装されたのは 2013-04-30 にリリースされた v0.9.27 ?)

何度か飲み会などで「素のJSとの連携が取りにくいからなー」とか言ってしまって誤解を与えてしまったのをここでお詫びします。
(export できるのは @tkihira さんに出来ますよと言われてやっと気がついたので、身近にJSXの熟練者がいなかったのが勘違いを持続した一因だとおもってます。JSX詳しい人ってどのへんに生息してるんでしょうか…)

悩んだところ

Array.<number> と Uint8Array の適切な扱い

zlib.js は Array.<number> と Uint8Array どちらも扱えるようになっているので、JSXにおいてもその仕様は維持したいと思っていました。
しかし、Array と Typed Array はどちらも Arraylike だけど継承関係にないので扱いにくい感じです。
結局は以下のように ByteArray というラッパーモジュールを作って、それを通すことで解決しました。
この ByteArray は constructor を持たずすべてのメソッドが static function なので、このモジュールに対する副作用は存在しないため、おそらくコンパイラが適切に最適化してくれるだろうなと推測して書きました。
(出力されたJSを確認したところ、期待通りこのモジュールの function call はきちんと展開されていました。便利)

final class ByteArray {
  static function subarray(array: Array.<number>, start: number, end: number): Array.<number> {
    return array.slice(start, end);
  }

  static function subarray(array: Uint8Array, start: number, end: number): Uint8Array {
    return array.subarray(start, end);
  }
  ...
}

JS のライブラリとして使えるように export

以下のように __export__ 修飾子を付ければ良いようです。
また、ここでも Array.<number>Uint8Array の扱いに困りましたが、とりあえず別のプロパティとしてあつかって振り分ける事にしました。

import '../src/inflate.jsx';

__export__ class ZlibInflate {
  var array: Inflate.<Array.<number>>;
  var uint8: Inflate.<Uint8Array>;
  var typed: boolean;

  function constructor(input : variant, options : Map.<variant>) {
    if (input instanceof Array.<number>) {
      this.array = new Inflate.<Array.<number>>(input as __noconvert__  Array.<number>);
      this.typed = false;
    } else if (input instanceof Uint8Array) {
      this.uint8 = new Inflate.<Uint8Array>(input as __noconvert__ Uint8Array);
      this.typed = true;
    } else {
      throw new Error('invalid input');
    }
  }

  function decompress() : variant {
    return this.typed ? this.uint8.decompress() : this.array.decompress();
  }
}

そして、これを以下のように jsxjsx-linker を使ってビルドする。

$ jsx --release --minify export/inflate.jsx | ./node_modules/.bin/jsx-linker -t export-global --stdin -o inflate.js

これだけでライブラリとして使えるようになります。便利。

性能

どきどきしながら計測したんですが、zlib.js とあまり違いは見られませんでした。
以下のページで確認できます。

また、ファイルサイズにおいても Array.<number>Uint8Array を受け付ける設計になっていたので JSX 版はそれぞれの型でメソッドを生成する必要があるため、zlib.js と比べて単純に倍程度のファイルサイズになってしまいました。
余計なコードが入っている訳ではないのでこの出力結果はかなり良いと思います。

終わりに

そこそこ使い慣れている JavaScript と、初心者状態である JSX の実装を比較するのはあまりフェアではないような気がしますが、今回は JavaScript とあまり変わらない結果になってしまいました。
JSX 熟練者の方々からのツッコミなどお待ちしております。