Ember.js をターミナルから触ってみる

最近 Ember.js にはまっています。 せっかくなので、ブラウザを使わずにターミナルからも ember を触れるようにしてみました。

使い方は簡単です。 まずはインストール。

$ npm install -g term-ember

そして実行します。

$ term-ember

あとは ember に触りたい放題です。

term ember> Ember.VERSION
'1.0.0-rc.3'
term ember> var App = Ember.Application.create();
...

ちなみに、各種ライブラリのバージョンを指定して起動することもできます。

$ term-ember --ember 1.0.0-rc2 --jquery 2.0.0

ちょっとしたメソッドの動作確認などに使うと便利かもしれません。

Node.js の EventEmitter のコードを読んで、速度最適化されたコードについて考える

大都会岡山 Advent Calendar 2012 20日目のエントリーです。
前回の記事は @DAI199 さんによる 中国で流行っていること、感じたこと(大都会岡山 AdventCalender2012 19日目) - tagamidaiki.com でした。

20 日目のエントリーは、大都会岡山で生まれ、大都会岡山のコミュニティの方々を尊敬し、そして札幌で暮らしている(!) @tricknotes が書かせていただきます。

ぼくの記事ではタイトルの通り、速度最適化された js コードについて見ていきたいと思います。

はじめに

「その書き方だと遅いから」「こっちの方が速いよ」なんていう言葉をちょくちょく耳にする機会があります。
でもそれって本当でしょうか?

コードの実行速度は処理系の実装と密に関係していて、パフォーマンス計測なしには語ることができません。
また、そのコードの実行のされ方によっても実行速度は大きく変わってくるでしょう。
(たとえば、実行時のホットスポットがどこになるかというのは、引数やオブジェクトの状態などに左右される場合があります。)

そこで今回は、 Node.jsEventEmitter を読んで、 JavaScript の速度最適化について考えてみることにしましょう。

Node.js を選んだ理由

Node.js はイベント駆動の非同期スタイルを強制していることで知られています。そして、このイベント駆動のインターフェースを提供しているモジュールが EventEmitter です。
Streamhttp.Server など多くの主要なモジュールがこの EventEmitter を継承することで、イベント駆動のインターフェースをユーザに提供しています。
(参考)

ということは、 EventEmitter 内でパフォーマンスの悪い箇所があれば、Node.js 全体のパフォーマンスに大きな影響を与えることになります。

実際に、この EventEmitter の中では多くの速度最適化が施されており、これが Node.js 全体の実行速度を維持するための重要なポイントとなっています。
(イベント名に __proto__ など、特定の名前が利用できないという不具合がありますが、それよりもスピードの方が重要であるという方針とのことです joyent/node#4366)

ではさっそく、この EventEmitter 中からいくつかコードを例に挙げながら、実際に速度を計測して本当に速度最適化されていることを確認していきましょう。

EventEmitterevents.js の中で定義されています。

本エントリーでの対象バージョンは Node.js 0.9.4-pre (9f4c0988) とします。

$ node -v
v0.9.4-pre
$ node
> process.versions
{ http_parser: '1.0',
  node: '0.9.4-pre',
  v8: '3.13.7.4',
  ares: '1.9.0-DEV',
  uv: '0.9',
  zlib: '1.2.3',
  openssl: '1.0.1c' }

最適化コードについて

○ Function#apply より Function#call

Function オブジェクトを、任意のオブジェクトをコンテキストとして実行するためのメソッドとして Function#apply と Function#call があります。
多くの処理系の場合と同じく、Node.js の場合もこの両者の間には大きな速度の差あるようです。

そのため、極力 Function#apply を使わないようなコードになっています。

だいぶ濃い感じになっていますね。
普通に書くと、以下のように1行で済むはずです。

handler.apply(this, Array.prototype.slice.call(arguments, 1));

実際、両者の間にどれくらいの速度差があるのか測定してみましょう。

// Function#apply と Function#call の比較
var count = 10000000;
var now, i;
var obj = {};
var key = 'key';

var fn = function() {};

now = Date.now();
for (i = 0; i < count; i++) {
  fn.apply(this, [1]);
}
console.log('* apply: %d ms', Date.now() - now);

now = Date.now();
for (i = 0; i < count; i++) {
  fn.call(this, 1);
}
console.log('* call: %d ms', Date.now() - now);
* apply: 519 ms
* call: 160 ms

たしかに Function#apply の方が遅いようですね。

○ delete より null

オブジェクトの特定のキーに紐付く値を削除する場合、値だけを削除する方法(null や undefined を代入する方法)とキーごと削除する方法(delete キーワードを使う方法)があります。Object#hasOwnProperty などを利用せず、値の truthy/falsy のチェックだけを行うのであれば、この両者の間で大きな動作の違いはありません。
ただ、Node.js の場合は前者の方が圧倒的に高速に動作します。
そのため、不要になったオブジェクトを破棄する際には、delete を利用せず null を代入するようになっています。

EventEmitter の中では、 EventEmitter#removeListenerEventEmitter#removeAllListeners の中で使われています。

// delete と null を代入の比較
var count = 1000000;
var now, i;
var obj = {};
var key = 'key';

now = Date.now();
for (i = 0; i < count; i++) {
  obj[key] = 1;
  obj[key] = null;
}
console.log('* obj[key] = null: %d ms', Date.now() - now);

now = Date.now();
for (i = 0; i < count; i++) {
  obj[key] = 1;
  delete obj[key];
}
console.log('* delete obj[key]: %d ms', Date.now() - now);
* obj[key] = null: 21 ms
* delete obj[key]: 405 ms

速度が処理系に依存することの参考までに、このベンチマークFireFox 16.0.2 (Max OS X) で実行してみました。
すると、以下のような結果となりました。

* obj[key] = null: 5520 ms
* delete obj[key]: 5857 ms

6%程度の差なのでそこまで大きな差ではありません。
この結果から、速度は処理系に依存していることがわかります。

○ Array#slice より for 文 & 代入

配列(もしくは arguments) を部分的に切り出したい場合、Array#slice を利用するのが簡単です。
ただ、Node.js では Array#slice を呼ばないように、 Array を要素数指定で初期化して、ひとつづつ値を詰めている箇所があります。

ここを簡単に記述するなら以下のようになります。

Array.prototype.slice.call(arguments, 1);

これについてはどの程度パフォーマンスに影響が出ているのでしょうか?

// Array#slice(1) と new Array() + for についての比較
var count = 10000000;
var now, i;
var obj = {};
var key = 'key';

var array = ['type', 1, 2, 3 /* any arguments */];

now = Date.now();
for (i = 0; i < count; i++) {
  array.slice(1);
}
console.log('* Array#slice(1): %d ms', Date.now() - now);

now = Date.now();
for (i = 0; i < count; i++) {
  var l = array.length;
  var _array = new Array();
  for (var j = 1; j < l; j++) {
    _array[j - 1] = array[j];
  }
}
console.log('* new Array(): %d ms', Date.now() - now);

now = Date.now();
for (i = 0; i < count; i++) {
  var l = array.length;
  var _array = new Array(l - 1);
  for (var j = 1; j < l; j++) {
    _array[j - 1] = array[j];
  }
}
console.log('* new Array(length): %d ms', Date.now() - now);
* Array#slice(1): 837 ms
* new Array(): 388 ms
* new Array(length): 267 ms

以上のことより、Array#slice を使った場合とそうでない場合では、速度差があることがわかります。

補足

ここまででは書いていませんでしたが、部分的にコードを取り出しただけのベンチマークでは不十分なケースもあります。

コードの書き方だけではなく、文脈に沿って最適化が行われる場合があるので、ちゃんとプロダクションコードに対してベンチーマークをとって全体で動かしてみるまで、速度最適化されたかどうかは判定できません。
(ということを @koichik さんに教えてもらいました。勉強になります!)


結論

ここまで見てきてみなさまお分かりでしょうが、速度に最適化することと人間に最適化することが共存できるとは限りません。速度に最適化し過ぎてリーダブルではないコードになってしまうと、メンテナンス性を著しく下げてしまうことでしょう。しかし、ここまでで見てきた EventEmitter の例のように、可読性を捨ててでも速度に最適化すべき場面というのも確かに存在します。
もちろんその場合には、プロジェクトの性質によってどこまで速度最適化をするかということを判断する必要があると思います。

また、本エントリーの内容は、Node.js 0.9.4-pre (9f4c0988) での挙動です。
今後、同じコードで同じ最適化の効果が得られると保証されているわけではありません。
(関連)

みなさまのアプリケーションでも、もしパフォーマンスが問題であると感じたときは、ホットスポットがどこにあるか計測しつつ、どこまで可読性を犠牲にするかということを考えながらチューニングしていくことをオススメします。

まとめ

以上でぼくの記事は終わりとさせていただきます。
次回の大都会岡山 Advent Calendar 2012sutorada さんです。
引き続き、大都会岡山 Advent Calendar 2012 をお楽しみください:-)

ブラウザ用に書かれた mocha のテストを Node.js で動作させる mocha-ci-driver を作ってみました

JavaScript のテストを作成する際、動作環境を意識したコードを書くことを手間に感じる方は多いかと思います。

そこで今回は、ブラウザ用に作成した JavaScript のテストコードを、 Node.js を利用した CI 環境でも同じように動作させることができるツールとして、 mocha-ci-driver を作ってみたのでご紹介したいと思います。


* ブラウザでのテスト

本来、ブラウザ用に書かれたテストは基本的にはブラウザでしか動作しません。
受け入れレベルのテストを selenium などを利用して動作させるというのはよくある手段ですが、モデルのみのテストだとなかなかそうもいきません。
そのため、JavaScript のテストをすべて CI に組み込んで動作させることは困難かと思います。

ひとつのアプローチとして、ブラウザでもサーバ(今回は Node.js を対象としています)でも動作するようなコードに書き換えるというのも一つの手段かもしれません。
ロジックだけのモデル層のテストなら互換性を考慮するのも可能でしょう。

(function(global) {
  // code
})(
  'undefined' === typeof exports ?
    window :
    module.exports
);

ただ、外部のライブラリや DOM に依存している部分についてはなかなか素直にはいきません。
例えば、 underscore や jQuery を利用していたら、その部分をブラウザ/サーバ用に差し替えるようなコードが必要になってくるでしょう。

(function(global, _) {
  // code
})(
  // 環境依存を解消するためのコード(本質的ではない)
  'undefined' === typeof window ?
    module.exports :
    window,
  'undefined' === typeof _ ?
    require('underscore') :
    _
);

この対応というのは、本質的なコードではない上にテストの信頼性も損なってしまいます。

またこの問題を解消できたとしても、DOM に依存するコードは単独でテストをしづらいという課題が残ります。


ブラウザで動作しているテストコードを、特別な変更無しで CI 環境でも動作させることができたら素敵ですよね。


というわけで、作ってみました。


* mocha-ci-driver を使うと

mocha-ci-driver は mocha で書かれているブラウザ用のテストを、Node.js 上でも動作させるためのドライバです。

これを使うと、ブラウザ用に書かれたテストを CI に組み込めます。
なので、普段の開発では CI で動作させておいて、ブラウザ互換を確認したいときは各ブラウザでテストを実行することができるようになります。


Node.js 用の設定はこんな感じです。

1. まず、ドライバを実行するためのファイルを追加します。
(値は各環境によって適宜変更してください。)

// ./test/driver.js
var Driver = require('mocha-ci-driver').Driver
  , basedir = __dirname + '/../'
  , port = 8080
  , testHtml = '/test/index.html'
  , driver = new Driver(basedir, port)

driver.run(testHtml);


2. 次に、普段利用しているテスト用の html を一部修正し、
html の中でなくドライバ側でテストを実行するよう設定します。
(mocha はテストを実行した後はテスト内容を捨ててしまうので、この設定が無いと、ドライバ側で実行すべきテストを取得できなくなってしまいます。)

(before)

<script>
  $(function () {
    mocha.run();
  });
</script>

(after)

<script>
  $(function () {
    // Node.js で実行時にはこのタイミングでテストを実行しない
    if (!/Node.js/.test(navigator.appName)) {
      mocha.run();
    }
  });
</script>

3. この設定が完了すれば、あとは Node.js で実行するだけです。

$ node ./test/driver.js


* mocha の対応バージョン

ただ、この mocha-ci-driver が対応しているのは、 visionmedia/mocha@a186b8dba1 以降になります。
2012.03.04 現在、 tag は切られていないので、 mocha-ci-driver を利用するためにはリポジトリから mocha を取得してきて make する必要があります。

$ git clone git://github.com/visionmedia/mocha.git mocha
$ cd mocha
$ make clean && make

これで mocha.js がビルドされるので、この mocha.js をテスト用の html で利用するようにしてください。


というわけで、 ブラウザでもサーバでも動作するテストに興味がある方は mocha-ci-driver 試してみてください:-)

Express のテンプレートエンジンとして haml + CoffeeScript を使う

Express の標準テンプレートエンジンは jade ですが、あの html の閉じタグを毎回書くのがぼくは好きになれません。
そこで、 haml をテンプレートエンジンとして利用するようにしてみようと思います。


これについては Express のガイドページにもやり方が書いてあります。
http://expressjs.com/guide.html

まず、 hamljs をインストールします。

$ npm install hamljs

そして Express を使うときに設定を行います。

# app.coffee
app = require('express').createServer()
app.register '.haml', require('hamljs')

閉じタグを書かなくてすむのはとてもすばらしいですね!


さて、これで haml を利用できるようになったわけですが、テンプレート中に書く JavaScript 部分も CoffeeScript を使ってお手軽に書けるようにしてみたいと思います。


haml には filter という機能があって、独自にコンパイラを指定できます。
この辺りは rubyhaml でも同じですね。

# app.coffee
app = require('express').createServer()

coffee = require 'coffee-script'
hamljs = require 'hamljs'

hamljs.filters.coffee = (str) ->
  @javascript coffee.compile(str)

app.register '.haml', require('hamljs')

こうしておくと、haml のテンプレート中で CoffeeScript を書けるようになります。

%head
  :coffee
    jQuery ($) ->
      alert "Generated from 'CoffeeScript'!"

すっきりと書けますね!
すばらしい!!

OSC 2011 Hokkaidoに参加しました

2011.06.11 に、オープンソースカンファレンス2011北海道に参加してきました。


OSCはぼくが初めて参加したIT系のイベントで、毎年参加することを楽しみにいます。そして今回で参加は3回目。


今回、発表の機会をいただけたのでNode.jsについて発表をさせていただきました。
Node.jsっていう名前は聞いたことあるけど、あまり触ったことがない人向けの内容でお話させてもらいました。


今回、LOCAL企画セミナーということで、次のようなテーマで発表しました。

  • 「破」 ”キーワードは聞いたことがあるけど、実際それってどんなものなの?”



ブレストに付き合ってくれた @ 、応援してくれた @ 、レビューに付き合ってくれた @ 、 @ 、 @ ありがとうございました。


そして、この素晴らしいイベントを開催してくださったスタッフのみなさま、ありがとうございました!!

expressを試してみる

node.jsからのー!expressを試してみました。
サーバサイドで動作するJavaScriptであるnode.js。
expressとは、そのnode.js上でRESTfulなアプリケーションを作成するためのフレームワークです。

本家:express

RubyのwebアプリケーションフレームワークであるSiratra風のAPIを提供しています。


環境

  • Ubuntu 10.04 LTS
  • node.js 0.1.96


まずはインストールから。

kiwiのインストール
kiwiとはnode.jsのパッケージマネージャです。

git clone git://github.com/visionmedia/kiwi.git
cd kiwi
sudo make install
kiwi -V
=> 0.3.0

expressのインストール

kiwi -v install express
kiwi list
=> express : 0.9.0

kiwiでインストールしたパッケージは、$HOME/.kiwi 以下に格納されます。
さっそくHelloWorldを試してみます。

mkdir express_text
cd express_text
// app.js
requier("kiwi").require("express");

get("/", function() {
    this.redirect("/home");
});

get("/home", function() {
    return "<h1>Hello World!</h1>"
});

run();

さっそく実行してみます。

node app.js
=> Express started at http://localhost:3000/ in development mode

さっそくブラウザからアクセスします。
おおっ!でてますね!「Hello World!」


expressではいくつかのテンプレートエンジンも使えるみたいです。
今回はHamlを使ってみます。

kiwi install haml
kiwi list
=> express : 0.9.0
   haml    : 0.4.1

さきほどのサンプルソースを改造してみます。

// app.js
requier("kiwi").require("express");

configure(function() {
    set("root", __dirname);
});

get("/", function() {
    this.redirect("/home");
});

get("/home", function() {
    this.render("home.html.haml", {
        locals: {
            message: {title: "Hello World!!", body: "I lovs node.js!"}
        }
    });
    // return "<h1>Hello World!</h1>"
});

run();

viewファイルも作っておきましょう。

mkdir views
// views/home.html.haml
%html
  %body
    %title= message.title
    %h1= message.title
    = message.body

アプリケーションを実行します。

node app.js

ブラウザから確認します。
正しく表示されていますね。


ドキュメントやexpressのサンプルを見る限るでは、テンプレートエンジンに、ejs(RubyのerbのJavaScript版)やその他のミドルウェア(RubyのRack風)が使える(ようになる?)みたいです。
まだそこまでの機能はそろってないみたいですが、これから発展が楽しみです。