Ninjaを使ってNode.jsをビルドする
今朝 Node.js でマージされたPull Request「 add configure option to build with ninja 」によって、Node.js が Ninja ビルドシステムに対応になりました。
Nodeの PaaSでは Nodejitsu や Node Ninjaなど忍者シリーズで命名されたものが思い当りますが、実は今回は全く関係ありません。
Ninjaビルドシステムは、米GoogleのChromiumプロジェクトの開発者、Evan Martin氏が開発した高速なビルドシステムです。このビルドシステムが作られた経緯や背景はこの記事「Chromiumの開発者、「Chrome」でも利用されているビルドシステム「Ninja」を公開」を参照してください。
1. NodeとNinjaの関係
Node.jsは、Googleから提供されている様々なオープンソーステクノロジーを活用しています。
- V8 JavaScriptエンジン
- GYP (Generate Your Project) マルチプラットフォームのメタビルドシステム
- gjslint JavaScriptのLintツール
このうちGYPは、Node.jsを各OSに適したビルドシステム(Unix系OS: make, Mac OS X: Xcode, Windows: Visual Studio)等を作るメタビルドシステムです。 Ninjaは、Chromeのビルド向けに開発されたこともあり、GYPがサポートしているビルドシステムです。(対象OSは Linux/Mac OS X/FreeBSD/Windows)
NodeのGYPについての解説は、以下のスライドで少し説明していますので、参考にどうぞ。
ここでは さくらVPS 1G Linux(Ubuntu12.04)のサーバ上でNinjaを使ってNodeをビルドします。
2. Ninja のインストール手順
まず git でNinjaのソース一式を持ってきます。
$ git clone git://github.com/martine/ninja.git Cloning into 'ninja'... remote: Counting objects: 6844, done. remote: Compressing objects: 100% (2272/2272), done. remote: Total 6844 (delta 4876), reused 6367 (delta 4453) Receiving objects: 100% (6844/6844), 3.18 MiB | 1012 KiB/s, done. Resolving deltas: 100% (4876/4876), done.
そしてビルド用のスクリプト bootstrap.py を実行するだけです。(おそらくpythonやC++コンパイラは必須でしょう)
$ cd ninja/ $ ./bootstrap.py Building ninja manually... Building ninja using itself... warning: re2c not found; changes to src/*.in.cc will not affect your build. wrote build.ninja. [21/21] LINK ninja Done!
そうしたらローカルディレクトリに ninja バイナリーができました。後はパスの通っているディレクトリに移すだけです。(私の場合 ~/bin にコピーしました)
$ ls -l ninja -rwxrwxr-x 1 ohtsu ohtsu 2292163 Sep 5 15:22 ninja $ ./ninja --version git $ cp ninja ~/bin/ $ which ninja /home/ohtsu/bin/ninja
3. Ninja で Node をビルドする。
これも簡単。configure のオプションで --ninja を付け加えて make するだけです。
$ ./configure --ninja { 'target_defaults': { 'cflags': [], 'default_configuration': 'Release', 'defines': [], 'include_dirs': [], 'libraries': []}, 'variables': { 'clang': 0, 'gcc_version': 46, 'host_arch': 'x64', 'node_install_npm': 'true', 'node_prefix': '', 'node_shared_openssl': 'false', 'node_shared_v8': 'false', 'node_shared_zlib': 'false', 'node_use_dtrace': 'false', 'node_use_etw': 'false', 'node_use_openssl': 'true', 'target_arch': 'x64', 'v8_no_strict_aliasing': 1, 'v8_use_snapshot': 'true'}} creating ./config.gypi creating ./config.mk $ make touch out/Makefile python tools/gyp_node -f ninja ninja -C out/Release/ ninja: Entering directory `out/Release/' [31/846] CC obj/deps/cares/src/cares.ares_options.o (途中コンパイルWarning がいっぱい) [846/846] LINK node ln -fs out/Release/node node $
はい、これで完了。無事 Node がビルドできました。パチパチ。
4. 本当に速いの? 性能評価
さぁ本当にビルドが速いんでしょうか? make とビルドの完了までの時間計測をしてみます。
まずは ninja から。
$ ./configure --ninja (中略) $ time make touch out/Makefile python tools/gyp_node -f ninja ninja -C out/Release/ ninja: Entering directory `out/Release/' (中略) [846/846] LINK node ln -fs out/Release/node node real 3m36.642s user 6m30.004s sys 0m33.390s
次に make のビルド時間の計測。 make の場合は -j 2 をつけて2並列で走らせます。(Ninja は default で並列度 -r 2 になっています。)
今回さくらVPSは、仮想2CPUなので並列度2でビルドします。
$ ./configure (中略) $ time make -j 2 make -C out BUILDTYPE=Release V=1 (中略) make[1]: Leaving directory `/home/ohtsu/tmp/github/node_build_make/out' ln -fs out/Release/node node real 3m44.649s user 6m28.120s sys 0m34.890s
あらっ、ほぼ一緒です。 Ninja 3分36秒、 Make 3分44秒。
それじゃ全部一気にビルドするより、普段やるようファイルが一部更新された場合のビルドが速いのかも?
ということで Ninja サポートされたコミットまで一度さかのぼって、それから差分更新でビルドした時間を比較します。といっても今朝なので2コミットしかありません。(でも最後のコミットが TLS のセッション対応なのでそれなりに大きい)
ではビルド時間を比較してみます。
$ git reset --hard HEAD~2 HEAD is now at 7b6d3ce build: add ninja support to Makefile $ ./configure --ninja (中略) $ make (中略) $ git pull origin master (中略) $ time make (中略) real 0m5.359s user 0m6.700s sys 0m0.624s
次に make での差分更新ビルドの経過時間。
$ git reset --hard HEAD~2 (前までほぼ一緒) $ time make -j 2 (中略) real 0m5.835s user 0m6.664s sys 0m0.756s
あらっ、そんなに違いないのか。
5. 結果とまとめ
ということで、以下の通りまとめました。
real時間 | Ninja | make | Ninja/make |
---|---|---|---|
全部ビルド | 3m36.642s | 3m44.649s | 0.964 |
HEAD~2更新ビルド | 0m5.359s | 0m5.835s | 0.918 |
Ninja Manualでは、Chromeは30,000以上のソースファイルがあって、1つのファイルを変更した時にビルドが開始するまで10秒かかっていたのが Ninja を使うと1秒以下になったと書いてあります。
Node だと、
$ git ls-files | grep \.cc$ | wc 314 314 9769
ぐらいです。
まとめ:まだまだNodeは、Ninjaビルドの高速化の恩恵にあずからない程度のソースファイル数なんでしょうね。
(誰かわかる人、理由を教えてください。)