文とハッシュ生成・探索のパフォーマンス

ふと見かけた、さまざまなフォーマットで日時を表示させるためのJavaScriptの記法いろいろ | IDEA*IDEAのエントリについてです。
ここで話題になっている、Dateオブジェクトをある書式に変換したいというのは、ままあるニーズです。
案件毎にフォーマットも違うので、毎回ガリゴリ書いちゃうんですよね><

確かによくまとまっているのですが、少し気になったことがあるので書き置きます。
テーマは残念な話。 ―配列への循環アクセス 2― - 外付Web海馬でも述べた、「スマートな書き方とパフォーマンス」のようなところです。

話題元の関数がよくまとまっている要因の一つは、英語表記の月名を配列から取得していることだと思います。
ではさっそく、見てみましょう。簡単のため、引数の型チェック等は省きます。

まずは、プログラミングの入門書っぽく。

function getMonthString1(monthIndex) {
	switch (monthIndex) {
		case 0: return 'January'; break;
		case 1: return 'February'; break;
		case 2: return 'March'; break;
		case 3: return 'April'; break;
		case 4: return 'May'; break;
		case 5: return 'June'; break;
		case 6: return 'July'; break;
		case 7: return 'August'; break;
		case 8: return 'September'; break;
		case 9: return 'October'; break;
		case 10: return 'November'; break;
		case 11: return 'December'; break;
	}
}

うーん、泥臭い><

次に、話題元っぽく。

function getMonthString2(monthIndex) {
	var monthString = [
		'January', 'February', 'March',
		'April', 'May', 'June',
		'July', 'August', 'September',
		'October', 'November', 'December'
	];
	return monthString[monthIndex];
}

うーん、スマート!

でもちょっと待って。

ここで、それぞれ関数の速度を計ってみましょ。

ストップウォッチを用意。
function getTheFuncOut(func, args, t) {
	args = args[0] ? args : [args];
	var start, end;
	start = new Date();
	for (var i=0, imax=t||10000; i<imax; i++) {
		func.apply(null, args);
	}
	end = new Date();
	return end - start;
}
計ってみる。
console.log(getMonthString1(6));	// July
console.log(getTheFuncOut(getMonthString1, 6) + 'ms');	// 62ms
console.log(getMonthString2(6));	// July
console.log(getTheFuncOut(getMonthString2, 6) + 'ms');	// 563ms
おっと、すごい差が出ました。

10000回呼ぶことは無いにしても、1ページにつき複数回呼ばれる可能性の高い関数でこの差はちょっと見逃せません。差の原因は、ハッシュの生成とそれの探索にかかるコストです。

ただ、話題元でも
ちょっとだけ使いたいときは自分で書きたいですよね。
とあるように、このスマートさは捨てがたい…。
そこで、次の様な書き方はどうでしょか。

JavaScriptっぽく

var getMonthString3 = new function() {
	var monthString = [
		'January', 'February', 'March',
		'April', 'May', 'June',
		'July', 'August', 'September',
		'October', 'November', 'December'
	];
	return function(monthIndex) {
		return monthString[monthIndex];
	}
};

console.log(getMonthString3(6));	// July
console.log(getTheFuncOut(getMonthString3, 6) + 'ms');	// 78ms

JSっぽいというかクロージャってやつですね。
メモリは食いますが、ハッシュの生成を一回に収めています。
どうしても泥臭いのよりは遅いですが、スマートさとパフォーマンスのバランスは良い感じじゃないでしょうか。

関数生成関数にも名前を付けて、引数に'jp'とか'en'とか渡して、みたいにすると汎用的に使えるかも知れません。応用して、「Dateオブジェクトをある書式に変換」を、'%year年%month月%date日(%day)'みたいな引数を元に関数を生成するなんてのも面白いですね。

おや?

ストップウォッチも結局毎回ガリゴリ書いちゃってますね><(前回)