Производительность оператора прототипа Javascript: экономит память, но быстрее ли? - PullRequest
45 голосов
/ 16 августа 2010

Я читаю здесь (Дуглас Крокфорд) с использованием оператора-прототипа для добавления методов в классы 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();

P.S.

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


РЕДАКТИРОВАТЬ: кому может быть интересно также сравнение производительности между объектом JS и статическим объектом JS может прочитать этот ответ ниже . Статические объекты определенно быстрее , очевидно, их можно использовать только тогда, когда вам не нужно более одного экземпляра объекта.

Ответы [ 9 ]

59 голосов
/ 16 августа 2010

Это был интересный вопрос, поэтому я выполнил несколько очень простых тестов (я должен был перезапустить свои браузеры, чтобы очистить память, но я этого не сделал; примите это во что бы то ни стало). Похоже, по крайней мере, в 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. Мне нравится, что мой объектный код инкапсулирован, и ему не дают дрейфа. Я думаю, когда скорость имеет значение, у меня нет выбора. Штопать.

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

31 голосов
/ 28 октября 2010

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

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

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

6 голосов
/ 14 ноября 2012

Так что я решил проверить это тоже.Я проверил время создания, время выполнения и использование памяти.Я использовал 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 байт на объект.

3 голосов
/ 17 августа 2010

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

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

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

2 голосов
/ 17 февраля 2017

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

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

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: я использовал ответ Эндрю в качестве ссылки.Используются те же циклы и обозначения.

1 голос
/ 29 августа 2016

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

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

Подается в каждой категории (10 000 итераций)

  • Только доступ к свойству (~ 0,5 мс) : { __proto__: Type }
  • Цикл создания объекта с доступом к свойству (<3 мс) </em>: 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 голос
/ 25 февраля 2015

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

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

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

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

0 голосов
/ 03 ноября 2010

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

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

0 голосов
/ 16 августа 2010

Я уверен, что в том, что касается создания объекта, он работает намного быстрее и также потребляет меньше памяти, в этом нет никаких сомнений, но я думаю, что движку javascript необходимо пройти через все свойства объекта, чтобы определить, если вызванное свойство / метод является частью этого объекта, а если нет, то проверьте его на прототип. Я не уверен на 100% в этом, но я предполагаю, что так оно и есть, и если это так, то в НЕКОТОРЫХ случаях, когда к вашему объекту добавлено МНОЖЕСТВО методов, созданных только один раз и использованных интенсивно, это может быть немного медленнее, но это всего лишь предположение, что я ничего не проверял.

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

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