Производительность оператора прототипа Javascript: экономит память, но быстрее ли это?

я прочитала здесь (Дуглас Крокфорд) использование оператора прототипа для добавления методов в классы Javascript сохраняет также.

тогда я читаю в эта статья Джона отставки "создание экземпляра функции с кучей свойств прототипа-это очень, очень, быстро", но он говорит об использовании прототипа стандартным способом, или он говорит о своем конкретном примере в своей статье?

для пример создания этого объекта:

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();

П. С.

Я знаю, что прототип используется для создания наследования и одноэлементного объекта и т. д. Но этот вопрос не имеет никакого отношения к этим предметам.


EDIT: кому это может быть интересно также сравнение производительности между объектом JS и статическим объектом JS можете узнать этот ответ ниже. статический объект, безусловно, быстрее, очевидно, они могут быть использованы только тогда, когда вам не нужно более одного экземпляра объекта.

9 ответов


это был интересный вопрос, поэтому я провел несколько очень простых тестов (я должен был перезапустить свои браузеры, чтобы очистить память, но я этого не сделал; возьмите это за то, что это стоит). Похоже, по крайней мере, на Safari и Firefox,prototype работает значительно быстрее [edit: не 20x, как указано ранее]. Я уверен, что тест в реальном мире с полнофункциональными объектами будет лучшим сравнением. Код, который я запустил, был таким (я запускал тесты несколько раз, отдельно):

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. Мне нравится моя объектный код, чтобы быть собственн-помещенное, и не пустили на самотек. Думаю, когда скорость имеет значение, у меня нет выбора. Штопать.

[Edit] большое спасибо @Kevin, который указал, что мой предыдущий код был неправильным, давая огромный импульс сообщаемой скорости prototype метод. После фиксации прототип по-прежнему работает значительно быстрее, но разница не такая огромная.


Я думаю, что это зависит от типа объекта, который вы хотите создать. Я провел аналогичный тест, как Эндрю, но со статическим объектом, и статический объект выиграл руки вниз. Вот тест:

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

результаты:

ИЕ6: время закрытия: 1062, прототип: 766, статический объект: 406

ИЕ8: время закрытия: 781, прототип: 406, статический объект: 188

FF: время закрытия: 233, время прототипа: 141, статическое время объекта: 94

сафари: время закрытия: 152, прототип: 12, статический объект: 6

хром: время закрытия: 13, Время прототипа: 8, статическое время Объекта: 3

извлеченный урок заключается в том, что если вы НЕ есть необходимость создать множество различных объектов из одного класса, а затем создать его как статический объект выигрывает руки вниз. Так что подумайте хорошенько, какой класс вам действительно нужен.


поэтому я решил проверить это. Я проверил время создания, время выполнения и использование памяти. Я использовал Nodejs v0.8.12 и тестовая платформа mocha, работающая на Mac Book Pro, загружается в Windows 7. "Быстрые" результаты используют прототипы, а "медленные" - шаблон модуля. Я создал 1 миллион объектов каждого типа, а затем получил доступ к 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 байт меньше на объект.


интуитивно кажется, что было бы более эффективно и быстрее создавать функции на прототипе: функция создается только один раз, а не каждый раз, когда создается новый экземпляр.

однако, будет небольшая разница в производительности, когда пришло время открыть функции. Когда c.showMsg ссылается, среда выполнения JavaScript сначала проверяет свойство на c. Если он не найден,.

Так, создание свойства на экземпляре приведет к несколько более быстрому времени доступа , но это может быть проблемой только для очень глубокой иерархии прототипов.


нам нужно разделить конструкцию и использование объекта.

при объявлении функции на прототипе она разделяется между всеми экземплярами. При объявлении функции в конструкторе она воссоздается при каждом создании нового экземпляра. Учитывая это, нам нужно отдельно оценивать строительство и использование, чтобы иметь лучшие результаты. Вот что я сделал и хочу поделиться с вами результатами. Этот эталон не проверяет скорость строительства.

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

из этих результаты мы видим, что версия прототипа является самой быстрой (4ms), но версия закрытия очень близка (7ms). Вам может понадобиться тест для вашего конкретного случая.

Так:

  • мы можем использовать версию прототипа, когда нам нужно иметь каждый бит производительности или обмениваться функциями между экземплярами.
  • мы можем использовать другие версии, когда нам нужны функции, которые они предоставляют. (частные заключения, читаемость так далее.)

PS: я использовал ответ Эндрю в качестве ссылки. Используются одни и те же петли и обозначения.


Я провел свои собственные тесты.

первый вывод заключается в том, что статический доступ на самом деле медленнее, чем реальное прототипирование. Интересно,версия 23 этого теста имеет дефектное прототипирование (переменная X) в нем, которое просто возвращает полностью переопределенный объект прототипа снова и снова, и когда я создавал свой тест, это прототипирование было еще медленнее, чем мой тест "реального прототипа".

в любом случае,ответ: если мой тест ошибочен, он показывает, что реальное прототипирование является самым быстрым. Он бьет или, по крайней мере, равен статическому объекту при игнорировании экземпляра. это-назначения на экземпляры и частные переменные намного медленнее. Я бы не подумал, что частные переменные будут такими медленными.

может быть интересно, что я расширил объект прототипа с помощью jQuery.протяните между ними, и это будет примерно такая же скорость, как и прямое назначение. Расширение было вне самого теста, конечно. По крайней мере, это способ обойти раздражающее письмо ".прототип.- ...Все время по частям.


тесты API производительности браузера высокого разрешения

ни один из тестов здесь не используетпроизводительность API для тестирования с высоким разрешением, поэтому я написал тот, который покажет текущие самые быстрые результаты для многих различных сценариев, включая 2, которые быстрее, чем любые другие ответы на большинстве запусков.

постишь в каждой категории (10 000 итераций)

  • доступ к собственности только (~0.5 ms): { __proto__: Type }
  • создание объекта цикла с доступом к свойствам (: Object.create(Type)

код использует ES6 без транспиляции babel для обеспечения точности. Он работает в текущем chrome. Выполнить тест ниже, чтобы увидеть разбивку.

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>

Я уверен, что, насколько экземпляр объекта идет, это намного быстрее, а также потребляет меньше памяти, никаких сомнений в этом, но я бы подумал, что движок javascript должен перебирать все свойства объекта, чтобы определить, является ли вызванное свойство/метод частью этого объекта, а если нет, то проверьте прототип. Я не на 100% уверен в этом, но я предполагаю, что это так работает, и если да, то в некоторых случаях, когда ваш объект имеет много методов, добавленных к нему, инстанцирован только один раз и используется сильно, тогда он может быть немного медленнее, но это просто предположение, что я ничего не тестировал.

но в конце концов, я все равно соглашусь, что в качестве общих правил использование прототипа будет быстрее.


таким образом, создание свойства в экземпляре приведет к немного более быстрому времени доступа, но это может быть проблемой только для очень глубокой иерархии прототипов.

на самом деле результат разный, то мы могли бы ожидать, чтобы получить прототип способов быстрее получить доступ к методам придает именно объект (ФФ проверял).