web-dev-qa-db-ja.com

Javascriptプロトタイプオペレーターのパフォーマンス:メモリを節約しますが、高速ですか?

私は here(Douglas Crockford) を使用して、Javascriptクラスにメソッドを追加するプロトタイプ演算子を使用しますメモリも保存します

次に、私は このJohn Resigの記事を読みました。プロトタイププロパティの束で関数をインスタンス化することは、非常に、非常に高速です "、しかし彼はプロトタイプを標準的な方法で使用することについて話しているのですか、それとも彼は彼の記事で彼の具体的な例について話しているのですか?

たとえば、次のオブジェクトを作成しています:

function Class1()
{
   this.showMsg = function(string) { alert(string); }
}
var c = new Class1();
c.showMsg();

このオブジェクトを作成するよりも遅いその後、?

function Class1() {}
Class1.prototype.showMsg = function(string) { alert(string); }
var c = new Class1();
c.showMsg();

追伸.

継承やシングルトンオブジェクトなどを作成するためにプロトタイプが使用されていることは知っています。しかし、この質問はこれらのサブジェクトとは関係ありません。


編集:JSオブジェクトとJS静的オブジェクトの間のパフォーマンス比較にも興味があるかもしれない人へ読むことができます この答えは以下 =。 静的オブジェクトは間違いなく高速です。明らかに、オブジェクトの複数のインスタンスが必要ない場合にのみ使用できます。

50
Marco Demaio

これは興味深い質問だったので、いくつかの非常に単純なテストを実行しました(メモリをクリアするためにブラウザを再起動する必要がありましたが、実行しませんでした。これは価値があるものと考えてください)。少なくともSafariとFirefoxでは、prototypeの実行速度が大幅に向上しているようです[編集:前述の20倍ではありません]。完全な機能を備えたオブジェクトを使用した実際のテストは、より優れた比較になると確信しています。私が実行したコードは次のとおりです(テストを個別に数回実行しました)。

var X,Y, x,y, i, intNow;

X = function() {};
X.prototype.message = function(s) { var mymessage = s + "";}
X.prototype.addition = function(i,j) { return (i *2 + j * 2) / 2; }

Y = function() {
    this.message = function(s) { var mymessage = s + "";}
    this.addition = function(i,j) { return (i *2 + j * 2) / 2; }
};


intNow = (new Date()).getTime();
for (i = 0; i < 1000000; i++) {
    y = new Y();
    y.message('hi');
    y.addition(i,2)
}
console.log((new Date()).getTime() - intNow); //FF=5206ms; Safari=1554

intNow = (new Date()).getTime();
for (i = 0; i < 1000000; i++) {
    x = new X();
    x.message('hi');
    x.addition(i,2)
}
console.log((new Date()).getTime() - intNow);//FF=3894ms;Safari=606

prototypeを使用するのが本当に嫌なので、本当に残念です。私は自分のオブジェクトコードを自己カプセル化し、ドリフトさせないようにしています。速度が問題になるときはあると思いますが、私には選択肢がありません。くそー。

[編集]以前のコードが間違っていたと指摘してくれた@Kevinに感謝します。報告されたprototypeメソッドの速度が大幅に向上しました。修正後、プロトタイプはまだかなり高速ですが、違いはそれほど大きくありません。

60
Andrew

作成するオブジェクトのタイプに依存すると思います。私はAndrewと同様のテストを実行しましたが、静的オブジェクトを使用し、静的オブジェクトが勝ちました。ここにテストがあります:

var X,Y,Z,x,y,z;

X = function() {};
X.prototype.message = function(s) { var mymessage = s + "";}
X.prototype.addition = function(i,j) { return (i *2 + j * 2) / 2; }

Y = function() {
    this.message = function(s) { var mymessage = s + "";}
    this.addition = function(i,j) { return (i *2 + j * 2) / 2; }
};

Z = {
 message: function(s) { var mymessage = s + "";}
 ,addition: function(i,j) { return (i *2 + j * 2) / 2; }
}

function TestPerformance()
{
  var closureStartDateTime = new Date();
  for (var i = 0; i < 100000; i++)
  {
 y = new Y();
    y.message('hi');
    y.addition(i,2);
  }
  var closureEndDateTime = new Date();

  var prototypeStartDateTime = new Date();
  for (var i = 0; i < 100000; i++)
  {
    x = new X();
    x.message('hi');
    x.addition(i,2);
  }
  var prototypeEndDateTime = new Date();

  var staticObjectStartDateTime = new Date();
  for (var i = 0; i < 100000; i++)
  {
 z = Z; // obviously you don't really need this
    z.message('hi');
    z.addition(i,2);
  }
  var staticObjectEndDateTime = new Date();
  var closureTime = closureEndDateTime.getTime() - closureStartDateTime.getTime();
  var prototypeTime = prototypeEndDateTime.getTime() - prototypeStartDateTime.getTime();
  var staticTime = staticObjectEndDateTime.getTime() - staticObjectStartDateTime.getTime();
  console.log("Closure time: " + closureTime + ", prototype time: " + prototypeTime + ", static object time: " + staticTime);
}

TestPerformance();

このテストは、私が見つけたコードの修正です:

http://blogs.msdn.com/b/kristoffer/archive/2007/02/13/javascript-prototype-versus-closure-execution-speed.aspx

結果:

IE6:閉鎖時間:1062、プロトタイプ時間:766、静的オブジェクト時間:406

IE8:閉鎖時間:781、プロトタイプ時間:406、静的オブジェクト時間:188

FF:閉鎖時間:233、プロトタイプ時間:141、静的オブジェクト時間:94

Safari:閉鎖時間:152、プロトタイプ時間:12、静的オブジェクト時間:6

Chrome:閉鎖時間:13、プロトタイプ時間:8、静的オブジェクト時間:3

学んだ教訓は、DO N'Tが同じクラスから多数の異なるオブジェクトをインスタンス化する必要がある場合、静的オブジェクトとして作成すると勝つということです。ダウン。そのため、本当に必要なクラスの種類について慎重に検討してください。

31
shmuel613

これもテストすることにしました。作成時間、実行時間、メモリ使用量をテストしました。 Nodejs v0.8.12と、Windows 7で起動したMac Book Proで動作するモカテストフレームワークを使用しました。「高速」の結果はプロトタイプを使用し、「低速」の結果はモジュールパターンを使用しています。各タイプのオブジェクトを100万個作成し、各オブジェクトの4つのメソッドにアクセスしました。結果は次のとおりです。

c:\ABoxAbove>mocha test/test_andrew.js

Fast Allocation took:170 msec
·Fast Access took:826 msec
state[0] = First0
Free Memory:5006495744

·Slow Allocation took:999 msec
·Slow Access took:599 msec
state[0] = First0
Free Memory:4639649792

Mem diff:358248k
Mem overhead per obj:366.845952bytes

? 4 tests complete (2.6 seconds)

コードは次のとおりです。

var assert = require("assert"), os = require('os');

function Fast (){}
Fast.prototype = {
    state:"",
    getState:function (){return this.state;},
    setState:function (_state){this.state = _state;},
    name:"",
    getName:function (){return this.name;},
    setName:function (_name){this.name = _name;}
};

function Slow (){
    var state, name;
    return{
        getState:function (){return this.state;},
        setState:function (_state){this.state = _state;},
        getName:function (){return this.name;},
        setName:function (_name){this.name = _name;}
    };
}
describe('test supposed fast prototype', function(){
    var count = 1000000, i, objs = [count], state = "First", name="Test";
    var ts, diff, mem;
    it ('should allocate a bunch of objects quickly', function (done){
        ts = Date.now ();
        for (i = 0; i < count; ++i){objs[i] = new Fast ();}
        diff = Date.now () - ts;
        console.log ("Fast Allocation took:%d msec", diff);
        done ();
    });
    it ('should access a bunch of objects quickly', function (done){
        ts = Date.now ();
        for (i = 0; i < count; ++i){
            objs[i].setState (state + i);
            assert (objs[i].getState () === state + i, "States should be equal");
            objs[i].setName (name + i);
            assert (objs[i].getName () === name + i, "Names should be equal");
        }
        diff = Date.now() - ts;
        console.log ("Fast Access took:%d msec", diff);
        console.log ("state[0] = " + objs[0].getState ());
        mem = os.freemem();
        console.log ("Free Memory:" + mem + "\n");
        done ();
    });
    it ('should allocate a bunch of objects slowly', function (done){
        ts = Date.now ();
        for (i = 0; i < count; ++i){objs[i] = Slow ();}
        diff = Date.now() - ts;
        console.log ("Slow Allocation took:%d msec", diff);
        done ();
    });
    it ('should access a bunch of objects slowly', function (done){
        ts = Date.now ();
        for (i = 0; i < count; ++i){
            objs[i].setState (state + i);
            assert (objs[i].getState () === state + i, "States should be equal");
            objs[i].setName (name + i);
            assert (objs[i].getName () === name + i, "Names should be equal");
        }
        diff = Date.now() - ts;
        console.log ("Slow Access took:%d msec", diff);
        console.log ("state[0] = " + objs[0].getState ());
        var mem2 = os.freemem();
        console.log ("Free Memory:" + mem2 + "\n");
        console.log ("Mem diff:" + (mem - mem2) / 1024 + "k");
        console.log ("Mem overhead per obj:" + (mem - mem2) / count + 'bytes');
        done ();
    });
});

結論:これは、この投稿の他のメンバーが見つけたものを裏付けています。常にオブジェクトを作成している場合、プロトタイプメカニズムは明らかに高速です。コードがオブジェクトのアクセスにほとんどの時間を費やしている場合、モジュールパターンはより高速です。メモリの使用に注意を払う必要がある場合、プロトタイプメカニズムはオブジェクトあたり約360バイト少なく使用します。

6
user1822264

直感的には、プロトタイプで関数を作成する方がメモリ効率がよく、高速になるようです。関数は一度だけ作成され、新しいインスタンスが作成されるたびに作成されるわけではありません。

ただし、関数がaccessになるときは、わずかなパフォーマンスの違いがあります。いつ c.showMsgが参照されている場合、JavaScriptランタイムは最初にcのプロパティを確認します。見つからない場合は、cのプロトタイプがチェックされます。

そのため、インスタンスでプロパティを作成すると、アクセス時間がわずかに速くなりますが、これは、プロトタイプ階層が非常に深い場合にのみ問題になる可能性があります。

3
harto

オブジェクトの構築と使用を分離する必要があります。

プロトタイプで関数を宣言すると、すべてのインスタンス間で共有されます。コンストラクタで関数を宣言すると、これは新しいインスタンスが作成されるたびに再作成されます。そのため、より良い結果を得るには、構築と使用を別々にベンチマークする必要があります。それは私がやったことであり、あなたと結果を共有したいと思います。このベンチマークは、構築速度をテストしません。

function ThisFunc() {
    this.value = 0;
    this.increment = function(){
        this.value++;
    }
}

function ProtFunc() {
    this.value = 0;
}

ProtFunc.prototype.increment = function (){
    this.value++;
}

function ClosFunc() {
    var value = 0;

    return {
        increment:function(){
            value++;
        }
    };
}

var thisInstance = new ThisFunc;

var iterations = 1000000;
var intNow = (new Date()).getTime();
for (i = 0; i < iterations; i++) {
    thisInstance.increment();
}
console.log(`ThisFunc: ${(new Date()).getTime() - intNow}`); // 27ms node v4.6.0

var protInstance = new ProtFunc;
intNow = (new Date()).getTime();
for (i = 0; i < iterations; i++) {
    protInstance.increment();
}
console.log(`ProtFunc: ${(new Date()).getTime() - intNow}`); // 4ms node v4.6.0

var closInstance = ClosFunc();
intNow = (new Date()).getTime();
for (i = 0; i < iterations; i++) {
    closInstance.increment();
}
console.log(`ClosFunc: ${(new Date()).getTime() - intNow}`); // 7ms node v4.6.0

これらの結果から、プロトタイプバージョンが最速(4ミリ秒)であるのに対し、クロージャバージョンは非常に近い(7ミリ秒)ことがわかります。特定のケースのベンチマークが必要になる場合があります。

そう:

  • すべてのパフォーマンスを実現する必要がある場合、またはインスタンス間で機能を共有する必要がある場合は、プロトタイプバージョンを使用できます。
  • 提供する機能が必要な場合は、他のバージョンを使用できます。 (プライベート状態のカプセル化、読みやすさなど)

PS:Andrewの回答を参照として使用しました。同じループと表記法を使用しました。

2
Vakhtang

高解像度ブラウザパフォーマンスAPIテスト

ここのテストはどれも高解像度テストの performance API を利用していないため、多くのさまざまなシナリオで現在最も速い結果を示すものを書きました。実行します。

各カテゴリで高速化(10,000反復)

  • プロパティアクセスのみ(〜0.5ms):_{ __proto__: Type }_
  • プロパティアクセスによるオブジェクト作成のループ(<3ms)Object.create(Type)

このコードは、正確さを保証するために、バベルトランスパイレーションなしのES6を使用しています。現在のクロムで動作します。以下のテストを実行して、内訳を確認します。

_function profile () {
  function test ( name
                , define
                , construct
                , { index = 0
                  , count = 10000
                  , ordinals = [ 0, 1 ]
                  , constructPrior = false
                  } = {}
                ) {
    performance.clearMarks()
    performance.clearMeasures()
    const symbols = { type: Symbol('type') }
    const marks = (
      { __proto__: null
      , start: `${name}_start`
      , define: `${name}_define`
      , construct: `${name}_construct`
      , end: `${name}_end`
      }
    )

    performance.mark(marks.start)
    let Type = define()
    performance.mark(marks.define)

    let obj = constructPrior ? construct(Type) : null
    do {
      if(!constructPrior)
        obj = construct(Type)
      if(index === 0)
        performance.mark(marks.construct)

      const measureOrdinal = ordinals.includes(index)
      if(measureOrdinal)
          performance.mark(`${name}_ordinal_${index}_pre`)

      obj.message('hi')
      obj.addition(index, 2)

      if(measureOrdinal)
        performance.mark(`${name}_ordinal_${index}_post`)
    } while (++index < count)
    performance.mark(marks.end)

    const measureMarks = Object.assign (
      { [`${name}_define`]: [ marks.start, marks.define ]
      , [`${name}_construct`]: [ marks.define, marks.construct ]
      , [`${name}_loop`]: [ marks.construct, marks.end ]
      , [`${name}_total`]: [ marks.start, marks.end ]
      }
    , ordinals.reduce((reduction, i) => Object.assign(reduction, { [`${name}_ordinal_${i}`]: [ `${name}_ordinal_${i}_pre`, `${name}_ordinal_${i}_post` ] }), {})
    )

    Object.keys(measureMarks).forEach((key) => performance.measure(key, ...measureMarks[key]))

    const measures = performance.getEntriesByType('measure').map(x => Object.assign(x, { endTime: x.startTime + x.duration }))
    measures.sort((a, b) => a.endTime - b.endTime)
    const durations = measures.reduce((reduction, measure) => Object.assign(reduction, { [measure.name]: measure.duration }), {})

    return (
      { [symbols.type]: 'profile'
      , profile: name
      , duration: durations[`${name}_total`]
      , durations
      , measures
      }
    )
  }

  const refs = (
    { __proto__: null
    , message: function(s) { var mymessage = s + '' }
    , addition: function(i, j) { return (i *2 + j * 2) / 2 }
    }
  )

  const testArgs = [
    [ 'constructor'
    , function define() {
        return function Type () {
          this.message = refs.message
          this.addition = refs.addition
        }
      }
    , function construct(Type) {
        return new Type()
      }
    ]
  , [ 'prototype'
    , function define() {
        function Type () {
        }
        Type.prototype.message = refs.message
        Type.prototype.addition = refs.addition
        return Type
      }
    , function construct(Type) {
        return new Type()
      }
    ]
  , [ 'Object.create'
    , function define() {
        return (
          { __proto__: null
          , message: refs.message
          , addition: refs.addition
          }
        )
      }
    , function construct(Type) {
        return Object.create(Type)
      }
    ]
  , [ 'proto'
    , function define() {
        return (
          { __proto__: null
          , message: refs.message
          , addition: refs.addition
          }
        )
      }
    , function construct(Type) {
        return { __proto__: Type }
      }
    ]
  ]

  return testArgs.reduce(
    (reduction, [ name, ...args ]) => (
      Object.assign( reduction
      , { [name]: (
            { normal: test(name, ...args, { constructPrior: true })
            , reconstruct: test(`${name}_reconstruct`, ...args, { constructPrior: false })
            }
          )
        }
      )
    )
  , {})
}

let profiled = profile()
const breakdown = Object.keys(profiled).reduce((reduction, name) => [ ...reduction, ...Object.keys(profiled[name]).reduce((r, type) => [ ...r, { profile: `${name}_${type}`, duration: profiled[name][type].duration } ], []) ], [])
breakdown.sort((a, b) => a.duration - b.duration)
try {
  const Pre = props => React.createElement('pre', { children: JSON.stringify(props.children, null, 2) })
  
  ReactDOM.render(React.createElement(Pre, { children: { breakdown, profiled } }), document.getElementById('profile'))
} catch(err) {
    console.error(err)
}_
_<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="profile"></div>_
1
cchamberlain

自分でテストを実行しました

最初の結論は、静的アクセスは実際には実際のプロトタイピングよりも遅いということです。興味深いことに、 このテストのバージョン2 にはプロトタイピング(変数X)に欠陥があり、完全にオーバーライドされたプロトタイプオブジェクトを何度も繰り返し返すだけです。テストを作成しているとき、このプロトタイピングはまだ「実際のプロトタイプ」テストよりも遅い。

とにかく、答えへの答え:私のテストに欠陥がない限り、それは実際のプロトタイピングが最も速いことを示しています。インスタンス化を無視すると、静的オブジェクトに勝る、または少なくとも静的オブジェクトと同じです。インスタンス化とプライベート変数に対するこの割り当ては、どちらもはるかに低速です。私は、プライベート変数がこれほど遅いとは思いませんでした。

JQuery.extendを間に置いてプロトタイプObjectを拡張したのは興味深いかもしれません。これは直接割り当てとほぼ同じ速度でした。もちろん、延長はテスト自体の範囲外でした。少なくともこれは、煩わしい ".prototype。"-Partsを常に書くことを回避する方法です。

1
x3c

オブジェクトのインスタンス化に関する限り、それははるかに高速でメモリ消費も少ないと確信していますが、JavaScriptエンジンは、オブジェクトのすべてのプロパティをループして、プロパティかどうかを判断する必要があると思います呼び出された/ methodはそのオブジェクトの一部であり、そうでない場合は、プロトタイプを確認します。私はこれについて100%確信はありませんが、それがどのように機能するかを仮定しています。そうであれば、オブジェクトに追加されたメソッドがたくさんあり、インスタンス化が1回だけで、頻繁に使用されている場合、それはおそらく少し遅いですが、それは私が何もテストしていないという仮定にすぎません。

しかし、最終的には、一般的なルールとして、プロトタイプを使用する方が高速になることにも同意します。

0
SBUJOLD