関数の引数を存在確認するための高速化Tips

2007.12.05 / javascript

(2007/12/12 追記)内容をまとめ直し+再考しました。

JavaScriptで関数の引数チェック行う場合に、「どんなチェック方法が速いのか?」な話。

たとえば、引数が存在する場合は「その引数」を、存在しない場合は「bar」を返す、という場面を想定します。単純に考えるとこんな感じ。

function check(arg){
if(arg) {
return arg;
} else {
return 'bar';
}
}

うーん、nullチェックをする、って方法も考えられます。

function check(arg){
if(arg != null){
return arg;
} else {
return 'bar';
}
}

typeof で確認って方法もありますね。

function check(arg){
if(typeof arg != 'undefined'){
return arg;
} else {
return 'bar';
}
}

条件を限定して、引数が「foo」の場合はfooを返す、という場合はこう書けます。

function check(arg){
if(arg=='foo'){
return arg;
} else {
return 'bar';
}
}

と、引数確認だけでもパっと思いついただけでも、これだけ思いつきます。(もっと全然違うパターンがあるよ!な場合はTBかコメントで教えてください!)

あと、もっと突っ込むとif文ではなく、三項演算子を使うとワンライナーでも書けてしまいます。たとえば一番最初の場合だと

function check(arg){
return arg || 'bar';
}

こんな感じに。(2007/12/05 15:00追記:この場合はワンライナーで書いてるけども、三項演算子を利用しているわけではないですね)

どう書けば速いの?

単純に疑問に思ったので、ざっとテストコードを書いてベンチマーク取ってみました。検証コードはこちら。

 

function checkStrNoIf(arg){
return (arg=='foo')? arg : 'bar';
}

function checkStrIf(arg){
if(arg=='foo'){
return arg;
} else {
return 'bar';
}
}


function checkArgsNoIf(arg){
return arg || 'bar';
}

function checkArgsIf(arg){
if(arg) {
return arg;
} else {
return 'bar';
}
}


function checkNullNoIf(arg){
return (arg!=null)? arg : 'bar';
}

function checkNullIf(arg){
if(arg != null){
return arg;
} else {
return 'bar';
}
}


function checkTypeNoIf(arg){
return (typeof(arg) != 'undefined')? arg : 'bar';
}

function checkTypeIf(arg){
if(typeof arg != 'undefined'){
return arg;
} else {
return 'bar';
}
}


// logging
function log(){
//return console.log.apply(this, arguments);
return print.apply(this, arguments);
}

// bench
var s, e;
var c = 100000;

s = new Date();
for(var i=0; i<c; i++){
checkStrNoIf();
}
e = new Date();
log("[checkStrNoIf]" + (e-s));


s = new Date();
for(var i=0; i<c; i++){
checkStrIf();
}
e = new Date();
log("[checkStrsIf]" + (e-s));


s = new Date();
for(var i=0; i<c; i++){
checkArgsNoIf();
}
e = new Date();
log("[checkArgsNoIf]" + (e-s));

s = new Date();
for(var i=0; i<c; i++){
checkArgsIf();
}
e = new Date();
log("[checkArgsIf]" + (e-s));


s = new Date();
for(var i=0; i<c; i++){
checkNullNoIf();
}
e = new Date();
log("[checkNullNoIf]" + (e-s));


s = new Date();
for(var i=0; i<c; i++){
checkTypeNoIf();
}
e = new Date();
log("[checkTypeNoIf]" + (e-s));

 

要するに、冒頭で上げた4ケースにそれぞれif文を使う場合、使わない場合でそれぞれ10万回ループで回して実行時間を比較。検証環境はFedora7上のSpiderMonkeyで。Firefox2上での比較、と考えてほぼ問題ないと思います。(SpiderMonkeyって何ぞや?とかインストール方法とかはまた別記事で書きます)

実行と検証

実行結果はこんな感じになりました。

$ js -f args.js
[checkStrNoIf]522
[checkStrIf]541
[checkArgsNoIf]538
[checkArgsIf]533
[checkNullNoIf]538
[checkTypeNoIf]592

なかなか面白い結果になっています。ざっくりまとめるとこんな感じ。

  • if文を使うより三項演算子の方が高速
  • typeofで比較する場合は三項演算子の方が顕著に高速
  • 文字列比較できる場合は、素直に文字列比較した方が良い
  • 文字列比較<引数確認<null確認<typeof確認
  • typeof確認はコストが大きい

まとめ

三項演算子ってif文のエイリアスみたいなもんだと思ってた。中身は等価というか。結構露骨に速度に関わってくるのが意外ですねぇ。コード読みづらくなるケースが多いけど、可能な限りで積極的に使っちゃっていいのかもしれません。三項演算子++

あと、typeofは結構使うケースが多いのも事実なのですが、これも可能な限り避ける方がよさそうですね。