JS 形式の設定ファイルを読み込むときは eval の代わりに new Function を使う

なぜか?
それはスコープチェーンが継承されるのを防ぐため。

具体的な例を見てみましょう。
以下のような、設定ファイルがあったとします。

[ conf1.js ]

{
  val1: 'abc',
  val2: 2
}

これを読み込みます。

eval 版
var work = 1;
var data = fs.readFileSync('/path/to/conf');
var conf = eval('(' + data + ')');
new Function 版
var work = 1;
var data = fs.readFileSync('/path/to/conf');
var conf = new Function('return ' + data)();

上の場合、eval 版、new Function 版ともに同じ結果なので問題ありません。

しかし、次のような設定ファイルの場合

{
  val1: 'abc',
  val2: work=2
}

eval 版だと、読み出し後に変数 work が破壊されます。
一方、new Function 版はスコープチェーンが切れているので work は 1 のまま。問題ありません。

nvm の新機能

今年も Node.js のユーザーカンファレンス「東京Node学園祭」が11月18日 (あと16日後)に開催されます。楽しみですねー。
以下、アドベントカレンダーの参加記事(19日目)になります。

nvm の新機能

nvm は Node.js をバージョン毎にインストールできる便利なツールです。先月、いくつか機能が追加されたので、そちらを紹介したいと思います。

インストール用ファイル

以前は nvm をインストールする時、github から git clone で取ってきて、手動で nvm.sh を実行したり .bash_profile を編集したりと面倒だったのですが、今はコンソールからコマンド一発でインストールできます。

curl https://raw.github.com/creationix/nvm/master/install.sh | sh

上のコマンドでインストールから .bash_profile の設定まで自動でやってくれます。

ls-remote

公開されている Node.js のバージョンを確認できます。

$ nvm ls-remote
   v0.1.14     v0.1.96      v0.4.1      v0.6.3      v0.7.7
   v0.1.15     v0.1.97      v0.4.2      v0.6.4      v0.7.8
   v0.1.16     v0.1.98      v0.4.3      v0.6.5      v0.7.9
   v0.1.17     v0.1.99      v0.4.4      v0.6.6     v0.7.10
   v0.1.18    v0.1.100      v0.4.5      v0.6.7     v0.7.11
   v0.1.19    v0.1.101      v0.4.6      v0.6.8     v0.7.12
   v0.1.20    v0.1.102      v0.4.7      v0.6.9      v0.8.0
   v0.1.21    v0.1.103      v0.4.8     v0.6.10      v0.8.1
   v0.1.22    v0.1.104      v0.4.9     v0.6.11      v0.8.2
   v0.1.23      v0.2.0     v0.4.10     v0.6.12      v0.8.3
   v0.1.24      v0.2.1     v0.4.11     v0.6.13      v0.8.4
   v0.1.25      v0.2.2     v0.4.12     v0.6.14      v0.8.5
   v0.1.26      v0.2.3      v0.5.0     v0.6.15      v0.8.6
   v0.1.27      v0.2.4      v0.5.1     v0.6.16      v0.8.7
   v0.1.28      v0.2.5      v0.5.2     v0.6.17      v0.8.8
   v0.1.29      v0.2.6      v0.5.3     v0.6.18      v0.8.9
   v0.1.30      v0.3.0      v0.5.4     v0.6.19     v0.8.10
   v0.1.31      v0.3.1      v0.5.5     v0.6.20     v0.8.11
   v0.1.32      v0.3.2      v0.5.6     v0.6.21     v0.8.12
   v0.1.33      v0.3.3      v0.5.7      v0.7.0     v0.8.13
   v0.1.90      v0.3.4      v0.5.8      v0.7.1     v0.8.14
   v0.1.91      v0.3.5      v0.5.9      v0.7.2      v0.9.0
   v0.1.92      v0.3.6     v0.5.10      v0.7.3      v0.9.1
   v0.1.93      v0.3.7      v0.6.0      v0.7.4      v0.9.2
   v0.1.94      v0.3.8      v0.6.1      v0.7.5      v0.9.3
   v0.1.95      v0.4.0      v0.6.2      v0.7.6
バイナリインストール
nvm install v0.9.3       ←バージョンは任意

以前は Node.js をインストールする時、ダウンロードしたソースコードをローカルでコンパイルしていましたが、現在はビルド済みのバイナリが用意されているので、それを落とすだけでインストール完了します。うちの環境だと昔は5分ぐらいかかっていたのが、今は 15 秒で終わります。感動的!
(なお、環境にマッチしたバイナリファイルがない場合は、これまで通りソースコードからコンパイル/ビルドされます。)

copy-packages

こちらは結構前(去年の 7 月)に追加された機能ですが、指定したローカル Node バージョンのグローバルモジュールを現在のバージョンのグローバルモジュールとして再インストールします。
たとえば、v0.9.3 をインストール後、v0.9.2 に入れていたグローバルモジュールを v0.9.3 でも使えるようにするときは以下のように打ちます。

$ nvm copy-packages v0.9.2

ディレクトリのコピーではなく、ネットからの再インストールなので、設定ファイルなどを変更していた場合は再度書き直す必要があります。

Node.js を Ninja でビルド

こちらの記事に Node.js を Ninja でビルドする方法がわかりやすく書いてあったので試してみました。
http://d.hatena.ne.jp/jovi0608/20120905/1346831489

実行環境 (Mac OS X 10.7.4 Node.js v0.9.2-pre)

結果

$ ./configure --ninja
$ time make
real  3m14.051s
user  9m43.755s
sys  0m52.738s
$ ./configure
$ time make -j 2
real  4m26.464s
user  7m45.149s
sys  0m55.281s
$ ./configure
$ time make
real  6m44.763s
user  5m56.184s
sys  0m45.123s

ninja さん、すごく速いです。すばらしい。

(9/9 追記)
実行マシンの CPU が 1.7 GHz Intel Core i5( コア数 : 2 スレッド数 : 4 ) なので make の並列度を 3 と 4 にあげて追加測定しました。

結果

$ ./configure
$ time make -j 3
real 3m34.384s
user 8m53.363s
sys  1m2.937s
$ ./configure
$ time make -j 4
real 3m29.327s
user 10m1.844s
sys  1m10.998s

おおう、並列度 3, 4 にするとかなり速くなりましたね。
Ninja さんは中で賢く CPU を調べて並列化してるのかな。

Node.js フロー制御モジュール速度比較(その2)

Node.js のバージョンを 0.9.1 、node-block のバージョンを 0.1.5 に上げてベンチマークを取り直しました。
ベンチマーク用のコードは前回と同じです。)

実行環境 (Mac OS X 10.7.4 Node.js v0.9.1)
async (v0.1.22)
step (v0.0.5)
node-block (v0.1.5)

$ time node bench-async.js
real  0m0.849s
user  0m0.824s
sys  0m0.028s
$ time node bench-step.js
real  0m0.266s
user  0m0.245s
sys  0m0.021s
$ time node bench-node-block.js
real  0m0.273s
user  0m0.259s
sys  0m0.013s

今回、node-block と Step がほぼ並んでいます。
node-block は、V8 組み込みの bind 関数を取っ払ったら、かなり速くなりました。

Node.js フロー制御モジュール速度比較

Node.js のフロー制御モジュールといえば async と step が有名ですね。
私は node-block という自作モジュールを使っているのですが、速度的なパフォーマンスが気になったのでベンチマークをとってみました。

実行環境 (Mac OS X 10.7.4 Node.js v0.9.0)
async (v0.1.22)
step (v0.0.5)
node-block (v0.1.4)

$ time node bench-async.js
real  0m0.848s
user  0m0.826s
sys  0m0.027s
$ time node bench-step.js
real  0m0.263s
user  0m0.241s
sys  0m0.022s
$ time node bench-node-block.js
real  0m0.370s
user  0m0.353s
sys  0m0.018s

グラフは右に長いほど遅くなります。
step がとても速いです。
一応 async と node-block のフォローをしとくと、async は多機能で、 node-block は step より書きやすいと思います。

[ bench-async.js ]

'use strict';
var async = require('async');

var loopCount = 20000;
(function serialLoop() {
  if (loopCount-- <= 0) return;
  async.series([
    function (cb) {
      process.nextTick(cb);
    },
    function(cb) {
      async.parallel([
        function(cb){process.nextTick(cb)},
        function(cb){process.nextTick(cb)}
      ], cb);
    },
    function(cb) {
      async.parallel([
        function(cb){process.nextTick(cb)},
        function(cb){process.nextTick(cb)},
        function(cb){process.nextTick(cb)}
      ], cb);
    },
    function(cb) {
      async.parallel([
        function(cb){process.nextTick(cb)},
        function(cb){process.nextTick(cb)},
        function(cb){process.nextTick(cb)},
        function(cb){process.nextTick(cb)}
      ], cb);
    }
  ],
  serialLoop);
})();

[ bench-step.js ]

'use strict';
var Step = require('step');

var loopCount = 20000;
(function serialLoop() {
  if (loopCount-- <= 0) return;
  Step(
    function () {
      process.nextTick(this);
    },
    function() {
      process.nextTick(this.parallel());
      process.nextTick(this.parallel());
    },
    function() {
      process.nextTick(this.parallel());
      process.nextTick(this.parallel());
      process.nextTick(this.parallel());
    },
    function() {
      process.nextTick(this.parallel());
      process.nextTick(this.parallel());
      process.nextTick(this.parallel());
      process.nextTick(this.parallel());
    },
    function() {
      serialLoop();
    }
  );
})();

[ bench-node-block.js ]

'use strict';
var block = require('node-block').block;

var loopCount = 20000;
(function serialLoop() {
  if (loopCount-- <= 0) return;
  block(
    function() {
      process.nextTick(this.async());
    },
    function() {
      process.nextTick(this.async());
      process.nextTick(this.async());
    },
    function() {
      process.nextTick(this.async());
      process.nextTick(this.async());
      process.nextTick(this.async());
    },
    function() {
      process.nextTick(this.async());
      process.nextTick(this.async());
      process.nextTick(this.async());
      process.nextTick(this.async());
    }
  )(serialLoop);
})();

Eclipse (Juno) で javascript を書けるようにする

先月リリースされた最新版 Eclipse 4.2 (Juno) を試そうとしたのですが、今回から javascript 用パッケージが廃止されたらしく、Node.js 向けの環境を作るのに一手間かかりました。

以下、環境構築メモ。

Classic 版をダウンロードする
http://www.eclipse.org/downloads/

Eclipse を起動
以下の手順で Web Developer Tools プラグインをインストール
 メニュー > Help > Install New Software
  work with: Juno - http://download.eclipse.org/releases/juno を選択
  プラグイン一覧から
   Web, Xml, JavaEE and OSGi Enterprise Development
    Eclipse Web Developer Tools ←をチェック
  nextボタンでインストール
  Eclipse 再起動

Node.js uncaughtException から Domain へ移行

今後 uncaughtException での実装は非奨励になり domain に置き換わるらしいです。
https://github.com/joyent/node/commit/e8af3405574dfee2cb8c11bf27195b774332db96

というわけで、移行のメモ。

process.on('uncaughtException', function(err) {
  console.log(err.message); 
});

// 何らかの処理

var domain = require('domain');
var d = domain.create();

d.on('error', function(err) {
  console.log(err.message); 
});
d.run(function() {
  // 何らかの処理
});

ついでに 移行前と移行後のベンチマークをとってみました。

$ time node bench-uncaughtException.js 
real  0m9.801s
user  0m8.197s
sys   0m3.544s
$ time node bench-domain.js 
real  0m11.794s
user  0m10.240s
sys   0m3.445s


実行環境はMBA。Node のバージョンは v0.8.2 です。
(7/20追記: Node v0.8.3 もほぼ同じ結果でした)
グラフは右に長いほど遅くなります。
ちょっと無視できない差が出ていますが、Node.js の開発も過渡期でしょうし今後に期待。

[ bench-uncaughtException.js ]

var fs = require('fs');

var cnt = 0;
process.on('uncaughtException', function(err) {
  cnt++; 
});
for (var i=0; i<200000; i++) {
  fs.readFile('not_found', 'utf-8', function(err, data) {
    if (err) throw err;
  });
}

[ bench-domain.js ]

var fs = require('fs');
var domain = require('domain');

var d = domain.create();
var cnt = 0;
d.on('error', function(err) {
  cnt++; 
});
d.run(function() {
  for (var i=0; i<200000; i++) {
    fs.readFile('not_found', 'utf-8', function(err, data) {
      if (err) throw err;
    });
  }
});