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

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

PS

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


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

Ответ 1

Это был интересный вопрос, поэтому я запускал очень простые тесты (я должен был перезапустить свои браузеры, чтобы очистить память, но я этого не сделал, возьмите это за то, что это стоит). Похоже, что на Safari и Firefox, prototype работает значительно быстрее [edit: not 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. После фиксации прототип все еще вокруг значительно быстрее, но разница не такая огромная.

Ответ 2

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

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

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

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

Ответ 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 байт меньше для каждого объекта.

Ответ 4

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

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

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

Ответ 5

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

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

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

Ответ 6

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

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

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

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

Ответ 7

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

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

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

  • Доступ к свойствам (~ 0,5 мс): { __proto__: Type }
  • Создание объекта Looping с доступом к свойствам (< 3ms): Object.create(Type)

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

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>

Ответ 8

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

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

Ответ 9

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

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