Javascriptクイズ(中級者向け):無名関数と実行効率の話
2007.12.13
Javascriptを使い始めたばかりの太郎君に、上司の花子さんから「スタイルシート用のスタイル名をDOMからアクセス可能なプロパティ名に変更するJavascriptのライブラリを作って欲しい」という課題が与えられました。何のことか分からずにポカンとしている太郎君に、花子さんは「"font-style" を"fontStyle"に変更する関数を作ればいいのよ」と言い残して立ち去ってしまいます。
O'ReillyのJavascript本としばらく格闘した太郎君は、やっとライブラリを完成させます。
function capitalize(str)
{
return str.charAt(1).toUpperCase();
}
function style2prop(str)
{
return str.replace(/-[a-z]/g, capitalize);
}
"font-style"が"fontStyle"にちゃんと変更されることを確認した太郎君は、コードレビューのために先輩の次郎君のところに行きます。すると次郎君は、「良くできたね」と褒めてはくれるのですが、「でも、これじゃあ"capitalize"っていう内部関数がライブラリの外から見えてしまうよね。こういうときは、無名関数を使って隠蔽すべきなんだよ」と言いながら、サラサラと書き直します。
function style2prop(str)
{
return str.replace(/-[a-z]/g, function(str) {
return str.charAt(1).toUpperCase();
});
}
「無名関数ってこういうところに使うんですね」と太郎君が感心していると、そこにちょうど通りかかったのが、Javascriptの実行効率に関してものすごくうるさいことで知られる三郎君。「なになに?見せて」という三郎君に、コードを見せると、
「これ、実行効率上少しオーバーヘッドがあるよね。内部関数を隠蔽したいならもっと良い方法があるよ」と言います。
さて、ここで問題です。三郎君が言っている「実行効率上のオーバーヘッド」とはいったいなんでしょう?そして、三郎君が言う「もっと良い方法」とはどんなものでしょう?
function style2prop(str)
{
function capitalize(str)
{
return str.charAt(1).toUpperCase();
}
return str.replace(/-[a-z]/g, capitalize);
}
ループ中の関数リテラルの生成コストを問題視しているのかなぁ?
repleceの正規表現が複数回ヒットする場合に関数リテラルが複数回呼ばれるので
2回目以降の関数リテラルに生成コストが発生すると仮定するとオーバーヘッドが発生する
と睨みましたが・・・
関数リテラルの評価時に動的要素がなければ、キャッシュしてしまえばすむ話なので
インタプリタがオプティマイズしてしまう気がして(というかしてくれないと困る)
という結論になって・・・
インタプリタがオプティマイズできるのであれば、上記の関数だとループが1回も回らないときにも
関数のパースをしてしまうので余計に重くなるという事を考えると・・・
実行効率悪くなってない?
という事でギブアップ
Posted by: 心は萌え | 2007.12.13 at 18:21
心は萌えさんと同じコードを考えましたが...
他に思い浮かびません。
そうか、これが中級なのか、道は遠いなぁ
Posted by: gk | 2007.12.13 at 18:36
str.charAt(1) を str[1] とすればテンポラリな文字列インスタンスを減らせるというあたりでしょうか?
タグ付き正規表現にすれば、直接対象の文字を得られますが、効率的に向上するかは・・・微妙。
リンク先では function style2prop(str) {...} を String.prototype.prop = function(){...} と書いていますが本質は同じです。
Posted by: ef | 2007.12.13 at 19:20
記事のタイトルが無名関数と実行効率の話
であるということから、
実処理の最適化は置いておくとして。
無名関数部分の解析が1回だけなのでこれが効率が良いです。
※実行環境による最適化を期待しないと仮定する
style2prop = function(){
var func = function(str)
{
return str.charAt(1).toUpperCase();
};
return function(str){
return str.replace(/-[a-z]/g, func);
};
}();
Posted by: MH | 2007.12.13 at 22:23
Javascriptに詳しくないので、アレですが
Javascriptで3項演算子って使えましたっけ?
使えるとすると
?:
function style2prop(str)
{
var func = null;
return str.replace(/-[a-z]/g, ((func==null)? (func = function capitalize(str)
{
return str.charAt(1).toUpperCase();
}
) : func));
}
というのが正解かな?
もっともオプティマイザが、
関数リテラルの評価は実行時まで遅延する
関数リテラルは生成されるたびに評価される
という条件が付いていればですけど・・・
まぁ、要するに実行効率というのは、オプティマイザや実装依存な事が多いので、
理論で語る前に、実践してプロファイル取れって事ですねorz
Posted by: 心は萌え | 2007.12.14 at 01:49
http://takadeko.blogspot.com/2007/12/blog-post_14.html
はじめまして.再帰呼び出しを利用してやってみました.
トラックバックを打たせてもらったのですが,なぜかどうにもうまくいかず:(
(もし幾つかいっているようでしたらすいません)
Posted by: takadeko | 2007.12.14 at 05:20
String.prototype.style2prop = function () {return this.replace(/-([a-z])/g, RegExp.$1.toUpperCase());}
Posted by: rakuto@ワンライナー | 2007.12.14 at 06:28
nessary変数は、コンパイラによるループ内の不変式や未使用コード除去を考慮したもの。
5 function style2prop(str)
6 {
7 function capitalize(str)
8 {
9 return str.charAt(1).toUpperCase();
10 }
11 return str.replace(/-[a-z]/g, capitalize);
12 }
13 String.prototype.style2prop = function () {return this.replace(/-([a-z])/g, RegExp.$1.toUpperCase());};
14
15 var i = 0, loop = 100000;
16 var nessary = '';
17 var t1 = 'moe', t2 = 'rakuto';
18 console.time(t1);
19 while(++i < loop) {
20 nessary = style2prop('font-style');
21 }
22 console.timeEnd(t1);
23
24 i = 0;
25 console.time(t2);
26 while(++i < loop) {
27 nessary = 'font-style'.style2prop();
28 }
29 console.timeEnd(t2);
moe: 2982ms
rakuto: 1826ms
Posted by: rakuto@ワンライナー | 2007.12.14 at 06:39
replaceValueにRegExpは使えませんね。
置換テキストも変更できませんでした。残念。
'z-atyle'.replace(/-([a-z])/g, ("$1".toUpperCase())); # => 'zastyle'
Posted by: rakuto@ワンライナー | 2007.12.14 at 07:06