ES6速度測定(クラスその他編)

速度検証最終回。落ち葉拾い。

class構文

インスタンス生成とメソッド呼び出しを計測。

function FVector(x,y,z){ this.x=x; this.y=y; this.z=z; }
FVector.prototype.len=function(){
    return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z);
};
class CVector {
    constructor(x,y,z){ this.x=x; this.y=y; this.z=z; }
    len(){
        return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z);
    }
}
const cv=new CVector(1,2,3), fv=new FVector(1,2,3);
console.log(cv, fv);
const tests=[
    function() {    // new CVector
        return new CVector(3,4,5);
    },
    function() {    // new FVector
        return new FVector(3,4,5);
    },
    function() {    // CVector.distance()
        return cv.len();
    },
    function() {    // FVector.distance()
        return fv.len();
    }
];

測定結果(Chrome)は、

[1] x 25,869,050 ops/sec ±1.49% (61 runs sampled)
[2] x 52,926,253 ops/sec ±0.86% (63 runs sampled)
[3] x 53,630,570 ops/sec ±1.13% (57 runs sampled)
[4] x 52,670,978 ops/sec ±1.37% (62 runs sampled)

メソッド呼び出しは同等ながら、インスタンス生成はclassの方が遅い。性能を気にする場面でnewしまくる事もあまりないので実害はなさそうだが、

class CVector {
    // ...中略...
    sub(vec){
        return new CVector(this.x-vec.x, this.y-vec.y, this.z-vec.z);
    }
}

もし上みたいな実装をするならば、functionの方で書いた方がよさそう。

テンプレート文字列

const url='http://www.google.com';
const txt='Google';
const tests=[
    function() {   // template string
        return `<a href="${url}">${txt}</a>`;
    },
    function() {   // +で接続
        return '<a href="'+url+'">'+txt+'</a>';
    }
];

配列.join(‘’)は、さすがにこのケースでは勝負にならないので除外。

測定結果(Chrome)は、

[1] x 24,174,987 ops/sec ±0.88% (62 runs sampled)
[2] x 26,209,296 ops/sec ±0.68% (62 runs sampled)

可読性が高いのに性能はほぼ同等。素晴らしい。

String#startsWith(), String#endsWith()

const str='abcdefghijklmnopqrstuvwxyz'.repeat(100);
const tests=[
    function(){     // startsWith()
        return str.startsWith('abc');
    },
    function(){     // substr(0,l)
        const t='abc';
        return (str.substr(0,t.length)==t);
    },
    function(){     // endsWith()
        return str.endsWith('xyz');
    },
    function(){     // substr(-l)
        const t='xyz';
        return (str.substr(-t.length)==t);
    }
];

比較対象はsubstr()。イマドキのChromeではlastIndexOf()やindexOf()を使うよりsubstr()の方が速いので。

測定結果(Chrome)は、

[1] x 16,466,257 ops/sec ±1.08% (62 runs sampled)
[2] x 26,997,860 ops/sec ±0.85% (63 runs sampled)
[3] x 19,073,732 ops/sec ±1.00% (62 runs sampled)
[4] x 23,324,930 ops/sec ±0.93% (60 runs sampled)

substr()が速いのか、それとも他の関数が遅いのか。

Math.hypot()

const pt={ x:12.3,y:45.6,z:78.9 };
const tests=[
    function(){ return Math.hypot(pt.x,pt.y) },
    function(){ return Math.hypot(pt.x,pt.y,pt.z) },
    function(){ return Math.sqrt(pt.x*pt.x+pt.y*pt.y) },
    function(){ return Math.sqrt(pt.x*pt.x+pt.y*pt.y+pt.z*pt.z) },
];

hypotはhypotenuse=斜辺。つまり意味的には2次元。JavaのMath.hypot()も引数2つ。 しかしjavascriptのMath.hypot()は可変長引数なのだ。

測定結果(Chrome)は、

[1] x 5,796,367 ops/sec ±0.92% (62 runs sampled)
[2] x 5,583,658 ops/sec ±0.90% (63 runs sampled)
[3] x 57,648,690 ops/sec ±1.93% (61 runs sampled)
[4] x 57,209,776 ops/sec ±1.27% (61 runs sampled)

圧倒的な遅さ。可変長引数という点も少しは影響してるだろうが、遅い理由の大部分はおそらく、JavaのMath.hypot()と同じ事情。sqrt()内の計算結果がdoubleの表現範囲からオーバーフローしないよう、面倒な事をしてくれているらしい。実際、Math.hypot(1e300,2e300)としても正しい結果が返ってくる。上の[3],[4]で同じ事をやればInfinityになる。日頃そんな計算はしそうにないけど、最小公倍数的な実装をしなければいけないのが共通関数のつらいところ。

その他

以下も一応計測はしたけど、あまりに当たり前すぎたので結論だけを書いておく。

  • アロー関数は、let self=this的な事をした場合と同等の性能。つまり、thisを拘束する必要がないなら(...)=>{...}の形よりfunction(...){...}の形にした方が僅かに速い。
  • デフォルトパラメータは、関数本体で引数 || デフォルト値的な事をした場合と同等の性能。当然、引数の省略を認めない方が僅かに速い。