JavaScript Advent Calendar 2011 (オレ標準コース) 17 日目、polygon_planet です。
ずっと Advent Calendar 参加してみたいなぁと思ってたんですが
ネタが思いつかない日々で半分諦めてたんですが、考え過ぎな気がしてきたので
別におもしろい記事でもないし、技術的にも参考になるのか不明ですがとりあえず書きます。
もしかしたら同じようなこと解説してる記事がすでにあるかも…(うまく検索できてない)
JavaScript で無名関数をその場で実行するとき、
ちなみに、
ただし、以下のように実行する場合はエラーではありません。
とりあえず、無名関数が生成され実行するまでの JavaScript コードの過程を追ってみます。
今回は手元に SpiderMonkey に付いてる JavaScript shell しかなかったので、それを使います。
わりと前にコンパイルしたやつですが JavaScript バージョンは 1.8 なので現行と大差ないと思います。
中間コードは js shell の dis で逆アセンブルして確認します。
まず (function(){})() から。
確認のため Mozilla のソースコードを見てます。
今回は js shell のバージョンに合わせるため
/mozilla-central/ ではなく /mozilla/ を参照します。
jsopcode.tbl
JSOP は jsopcode.c あたりで確認できます。
JavaScript バイトコードインタプリタの jsinterp.c を参照して無名関数の生成から追ってみます。
jsinterp.c
じゃあ JS_GET_SCRIPT_FUNCTION はどうなってるの
jsscript.h
(これは現行の JavaScript だともっとてっとり早くなってる)
こんな感じで追ってみると、anonfunobj は無名関数の生成ということがわかります。
次の group は、
jsopcode.tbl で同じく定義されています。
無名関数オブジェクトを生成し、それを括弧 () によって式とし、
そしてそれを call というような流れになっています。
続いて (function(){}()) のほうも同じように。
なんと、(function(){})() と比べると
group がないだけで、ほかは同じになってしまいました。
無名関数に対する Parenthesization が発生しないので、
ちょうど、上で書いた
この結果から、
とは言っても、どちらを使用しても実行時間が変化するとしてもマイクロ秒単位などで
JavaScript でそこまでクリティカルな処理が存在するのかも微妙なところで
あまり気にしないでいい気がします。
でも一応ベンチマークをとってみます。
結果:
たしかに (function(){}()) のほうが速かったんですが、
10,000,000 回ループして わずか 48 ミリ秒の違い。
これを懸念してる人って…(いるのでしょうか)
そういえばこの違いについてはちょっと前に Twitter で話題(?) になって気がします。
もしかしてもうまとめられてたのかな…。だとしたら……考えたくない。
とりあえずモヤモヤした感じがなくなったのでよかった。
たまにソースコード確認してみると新鮮デスネ!
ずっと Advent Calendar 参加してみたいなぁと思ってたんですが
ネタが思いつかない日々で半分諦めてたんですが、考え過ぎな気がしてきたので
別におもしろい記事でもないし、技術的にも参考になるのか不明ですがとりあえず書きます。
もしかしたら同じようなこと解説してる記事がすでにあるかも…(うまく検索できてない)
JavaScript で無名関数をその場で実行するとき、
(function() {という書き方が主流っぽいですが、
// 処理
})();
(function() {こう書いたらどう違うのか。
// 処理
}());
ちなみに、
function() {これは式ではなく文として扱われるためエラーです。
}(); // エラー
ただし、以下のように実行する場合はエラーではありません。
var arr = [function(){}()]; // [undefined]
var obj = {foo: function(){}()}; // {foo: undefined}
(function(){})() vs (function(){}())
どちらも同じように実行され、結果的には同じなのでとりあえず、無名関数が生成され実行するまでの JavaScript コードの過程を追ってみます。
今回は手元に SpiderMonkey に付いてる JavaScript shell しかなかったので、それを使います。
わりと前にコンパイルしたやつですが JavaScript バージョンは 1.8 なので現行と大差ないと思います。
中間コードは js shell の dis で逆アセンブルして確認します。
まず (function(){})() から。
$ jsanonfunobj というは、無名関数を定義していると思われますが、
js> dis(function() { (function(){})() });
flags: LAMBDA INTERPRETED
main:
00000: anonfunobj (function () {})
00003: group
00004: pushobj
00005: call 0
00008: pop
00009: stop
Source notes:
0: 5 [ 5] pcbase offset 5
確認のため Mozilla のソースコードを見てます。
今回は js shell のバージョンに合わせるため
/mozilla-central/ ではなく /mozilla/ を参照します。
jsopcode.tbl
305 /* Auto-clone (if needed due to re-parenting) and push an anonymous function. */このあたりの JSOP_* から参照できます。
306 OPDEF(JSOP_ANONFUNOBJ, 128, "anonfunobj", NULL, 3, 0, 1, 19, JOF_OBJECT)
JSOP は jsopcode.c あたりで確認できます。
3777 case JSOP_ANONFUNOBJ:ここでは LOAD_FUNCTION が使われています。
3778 #if JS_HAS_GENERATOR_EXPRS
3779 sn = js_GetSrcNote(jp->script, pc);
3780 if (sn && SN_TYPE(sn) == SRC_GENEXP) {
3781 JSScript *inner, *outer;
3782 void *mark;
3783 SprintStack ss2;
3784
3785 LOAD_FUNCTION(0);
3786 inner = fun->u.i.script;
// ... (長いので省略) ...
JavaScript バイトコードインタプリタの jsinterp.c を参照して無名関数の生成から追ってみます。
5744 BEGIN_CASE(JSOP_ANONFUNOBJ)ここでも LOAD_FUNCTION が使われているので定義を追ってみます。
5745 /* Load the specified function object literal. */
5746 LOAD_FUNCTION(0);
5747
5748 /* If re-parenting, push a clone of the function object. */
5749 parent = js_GetScopeChain(cx, fp);
5750 if (!parent)
5751 goto error;
5752 obj = FUN_OBJECT(fun);
5753 if (OBJ_GET_PARENT(cx, obj) != parent) {
5754 obj = js_CloneFunctionObject(cx, fun, parent);
5755 if (!obj)
5756 goto error;
5757 }
5758 PUSH_OPND(OBJECT_TO_JSVAL(obj));
5759 END_CASE(JSOP_ANONFUNOBJ)
jsinterp.c
2604 #define LOAD_FUNCTION(PCOFF) \JS_GET_SCRIPT_FUNCTION を実行するマクロになっています。
2605 JS_GET_SCRIPT_FUNCTION(script, GET_FULL_INDEX(PCOFF), fun)
じゃあ JS_GET_SCRIPT_FUNCTION はどうなってるの
jsscript.h
143 #define JS_GET_SCRIPT_FUNCTION(script, index, fun) \JSFunction にキャストして JavaScript 関数オブジェクトになることが確認できました。
144 JS_BEGIN_MACRO \
145 JSObject *funobj_; \
146 \
147 JS_GET_SCRIPT_OBJECT(script, index, funobj_); \
148 JS_ASSERT(HAS_FUNCTION_CLASS(funobj_)); \
149 JS_ASSERT(funobj_ == (JSObject *) STOBJ_GET_PRIVATE(funobj_)); \
150 (fun) = (JSFunction *) funobj_; \
151 JS_ASSERT(FUN_INTERPRETED(fun)); \
152 JS_END_MACRO
(これは現行の JavaScript だともっとてっとり早くなってる)
こんな感じで追ってみると、anonfunobj は無名関数の生成ということがわかります。
次の group は、
jsopcode.tbl で同じく定義されています。
317 /* Parenthesization opcode to help the decompiler. */jsopcode.c では以下のあたり。
318 OPDEF(JSOP_GROUP, 131, "group", NULL, 1, 0, 0, 19, JOF_BYTE)
2066 case JSOP_GROUP:
2067 cs = &js_CodeSpec[lastop];
2068 if ((cs->prec != 0 &&
2069 cs->prec <= js_CodeSpec[NEXT_OP(pc)].prec) ||
2070 pc[JSOP_GROUP_LENGTH] == JSOP_NULL ||
2071 pc[JSOP_GROUP_LENGTH] == JSOP_DUP ||
2072 pc[JSOP_GROUP_LENGTH] == JSOP_IFEQ ||
2073 pc[JSOP_GROUP_LENGTH] == JSOP_IFNE) {
// ...(中略)...
2100 }
無名関数オブジェクトを生成し、それを括弧 () によって式とし、
そしてそれを call というような流れになっています。
続いて (function(){}()) のほうも同じように。
$ js
js> dis(function() { (function(){}()) });
flags: LAMBDA INTERPRETED
main:
00000: anonfunobj (function () {})
00003: pushobj
00004: call 0
00007: pop
00008: stop
Source notes:
0: 4 [ 4] pcbase offset 4
なんと、(function(){})() と比べると
group がないだけで、ほかは同じになってしまいました。
無名関数に対する Parenthesization が発生しないので、
ちょうど、上で書いた
[function(){}()]
のように実行されています。この結果から、
(function(){})() よりも、 (function(){}()) のほうが
コストが減るとわかりました。
コストが減るとわかりました。
とは言っても、どちらを使用しても実行時間が変化するとしてもマイクロ秒単位などで
JavaScript でそこまでクリティカルな処理が存在するのかも微妙なところで
あまり気にしないでいい気がします。
でも一応ベンチマークをとってみます。
js> for (var t = +new Date, i = 0, f; i < 10000000; i++) f = (function(){}());
print(+new Date - t);
10437
js> for (var t = +new Date, i = 0, f; i < 10000000; i++) f = (function(){})();
print(+new Date - t);
10485
結果:
- (function(){}()) : 10437 ms.
- (function(){})() : 10485 ms.
たしかに (function(){}()) のほうが速かったんですが、
10,000,000 回ループして わずか 48 ミリ秒の違い。
これを懸念してる人って…(いるのでしょうか)
そういえばこの違いについてはちょっと前に Twitter で話題(?) になって気がします。
もしかしてもうまとめられてたのかな…。だとしたら……考えたくない。
とりあえずモヤモヤした感じがなくなったのでよかった。
たまにソースコード確認してみると新鮮デスネ!