js_of_ocamlとBatteries Includedの相性について

Posted on June 8, 2014

Batteries Includedを利用したOCamlコードをjs_of_ocamlで変換して動作するところまで確認したので報告する。

確認環境

確認に使用したOCamlコード

以下のコードを使う。

(* a.ml *)
open Batteries

let () = List.iter print_int [1; 2; 3]

なお、以下のように必要なモジュールを注意深く選べば、バイトコードのコードサイズを減らせるが、今回は何も考えずにopen Batteriesすることにした。

module List = BatList

バイトコードへのコンパイルはいつも通り行なう。

$ ocamlfind ocamlc -package batteries -linkpkg a.ml
$ ./a.out
123

確認手順

まずは試しに変換してみる。

$ js_of_ocaml a.out
There are some missing primitives
Dummy implementations (raising 'Failure' exception) will be used if they are not available at runtime.
You can prevent the generation of dummy implementations with the commandline option '-disable genprim'
Missing primitives provided by +weak.js:
  caml_weak_blit
  caml_weak_check
  caml_weak_create
  caml_weak_get
  caml_weak_get_copy
  caml_weak_set
Missing primitives:
  create_nat
  incr_nat
  initialize_nat
  mult_digit_nat
  set_digit_nat
  set_to_zero_nat

いくつかのプリミティブが足りないと報告された。caml_weak_*は弱参照(Weakモジュール)のプリミティブで、コマンドラインに+weak.jsを加えれば解決するらしい。(ただし、ちゃんとした弱参照のセマンティクスは提供されない1

*_natは、どうやらnumライブラリのプリミティブのようだ。numライブラリ相当をJavaScriptで実装するのは骨が折れるので、numライブラリが実質的に使われないことを祈った上で、空の実装でごまかす。2

(* batteries_runtime.ml *)
//Provides: initialize_nat
function initialize_nat() {}

//Provides: create_nat
function create_nat() {}

//Provides: set_to_zero_nat
function set_to_zero_nat() {}

// Provides: set_digit_nat
function set_digit_nat() {}

// Provides: incr_nat
function incr_nat() {}

// Provides: mult_digit_nat
function mult_digit_nat() {}

これで問題なく変換できるはず。

$ js_of_ocaml +weak.js batteries_runtime.js a.out

実行してみる。

$ node a.js
123

大丈夫そう。

コードサイズと変換速度

js_of_ocamlはさまざまなコンパイラ技術を駆使して最適化されたJavaScriptを生成する3。そのためjs_of_ocamlの変換速度が気になってくる。今回利用したバイトコードでは以下のようになった。

バイトコードのコードサイズ:

$ ls -lh a.out
-rwxr-xr-x 1 tomo tomo 964K  6月  8 01:42 a.out

js_of_ocamlの実行時間:

$ time js_of_ocaml +weak.js batteries_runtime.js a.out
js_of_ocaml +weak.js batteries_runtime.js a.out  1.44s user 0.04s system 99% cpu 1.485 total

変換後のJavaScriptのコードサイズ:

$ ls -lh a.js
-rw------- 1 tomo tomo 71K  6月  8 02:41 a.js

Closure Compilerをかけた場合のJavaScriptのコードサイズ:

$ time closure-compiler a.js > a.opt.js
closure-compiler a.js > a.opt.js  15.20s user 0.17s system 249% cpu 6.160 total
$ ls -lh a.opt.js
-rw-r--r-- 1 tomo tomo 47K  6月  8 02:50 b.js

ついでにもう少し規模の大きいものについても調べておく。

$ ls -lh a.out
-rwxr-xr-x 1 tomo tomo 9.3M  6月  8 05:08 a.out
$ time js_of_ocaml +weak.js batteries_runtime.js a.out
js_of_ocaml +weak.js batteries_runtime.js a.out  2.72s user 0.05s system 98% cpu 2.794 total
$ ls -lh a.js
-rw------- 1 tomo tomo 346K  6月  8 05:10 a.js

これはocamlyacc/ocamllexを使った割と本格的なインタプリタなのだが、ブラウザ上でもちゃんと動作することを確認した。

結論

対応の手間とJavaScriptのコードサイズから考えると、js_of_ocamlとBatteries Includedの相性はまずまずだと思われる。


  1. JavaScriptに弱参照がないから?

  2. これをうまく使えばあるいは

  3. “From bytecode to JavaScript: the Js_of_ocaml compiler”を参照