Javascript prototype operator performance: ahorra memoria, pero ¿es más rápido?


He leído aquí (Douglas Crockford)usando el operador prototipo para agregar métodos a las clases de Javascript también ahorra memoria.

Entonces leí en este artículo de John Resig "Crear instancias de una función con un montón de propiedades de prototipo es muy, muy, rápido", pero, ¿está hablando de usar prototype de la manera estándar, o está hablando de su ejemplo específico en su artículo?

Por ejemplo, está creando esto objeto:

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

Más lento que creando este objeto, entonces?

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

P.d.

Sé que prototype se usa para crear objetos de herencia y singleton, etc. Pero esta pregunta no tiene nada que ver con estos temas.


EDITAR: para quien pueda estar interesado también en comparación de rendimiento entre un objeto JS y un objeto estático JS puede leer esta respuesta a continuación. Los objetos estáticos son definitivamente más rápidos , obviamente pueden ser se utiliza solo cuando no se necesita más de una instancia del objeto.

Author: Community, 2010-08-16

9 answers

Fue una pregunta interesante, así que ejecuté algunas pruebas muy simples (debería haber reiniciado mis navegadores para limpiar la memoria, pero no lo hice; tome esto por lo que vale la pena). Parece que al menos en Safari y Firefox, prototype se ejecuta significativamente más rápido [editar: no 20x como se indicó anteriormente]. Estoy seguro de que una prueba del mundo real con objetos con todas las funciones sería una mejor comparación. El código que corrí fue este (corrí las pruebas varias veces, por separado):

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

Es una verdadera vergüenza, porque yo realmente odio usar prototype. Me gusta que mi código objeto sea auto-encapsulado, y no se le permita derivar. Supongo que cuando la velocidad importa, sin embargo, no tengo elección. Maldito.

[Editar] Muchas gracias a @Kevin que señaló que mi código anterior estaba equivocado, dando un gran impulso a la velocidad reportada del método prototype. Después de la fijación, el prototipo sigue siendo significativamente más rápido, pero la diferencia no es tan enorme.

 56
Author: Andrew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-17 02:20:24

Supongo que depende del tipo de objeto que desea crear. Hice una prueba similar a Andrew, pero con un objeto estático, y el objeto estático ganó sin duda. Aquí está la prueba:

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

Esta prueba es una modificación del código que encontré en:

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

Resultados:

IE6: tiempo de cierre: 1062, tiempo de prototipo: 766, tiempo del objeto estático: 406

IE8: tiempo de cierre: 781, tiempo de prototipo: 406, tiempo de objeto estático: 188

FF: tiempo de cierre: 233, tiempo de prototipo: 141, tiempo de objeto estático: 94

Safari: tiempo de cierre: 152, tiempo de prototipo: 12, tiempo de objeto estático: 6

Chrome: tiempo de cierre: 13, tiempo de prototipo: 8, tiempo de objeto estático: 3

La lección aprendida es que si NO tiene la necesidad de crear instancias de muchos objetos diferentes de la misma clase, luego, crearlo como un objeto estático gana sin duda. Así que piensa cuidadosamente qué clase realmente necesitas.

 30
Author: shmuel613,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-06 22:23:21

Así que decidí probar esto también. Probé el tiempo de creación, el tiempo de ejecución y el uso de la memoria. Usé Nodejs v0.8.12 y el marco de prueba mocha que se ejecuta en un Mac Book Pro arrancado en Windows 7. Los resultados 'rápidos' están usando prototipos y los 'lentos' están usando patrón de módulo. Creé 1 millón de cada tipo de objeto y luego accedí a los 4 métodos en cada objeto. Aquí están los resultados:

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)

El código es El siguiente:

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

Conclusión: Esto respalda lo que otros en este post lo han encontrado. Si está creando objetos constantemente, el mecanismo del prototipo es claramente más rápido. Si su código pasa la mayor parte de su tiempo accediendo a objetos, entonces el patrón del módulo es más rápido. Si es sensible al uso de memoria, el mecanismo del prototipo usa ~360 bytes menos por objeto.

 6
Author: user1822264,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-13 22:51:52

Intuitivamente, parece que sería más eficiente en memoria y más rápido crear funciones en el prototipo: la función solo se crea una vez, no cada vez que se crea una nueva instancia.

Sin embargo, habrá una ligera diferencia de rendimiento cuando sea el momento de acceder a la función. Cuando se hace referencia a c.showMsg, el tiempo de ejecución de JavaScript comprueba primero la propiedad en c. Si no se encuentra, se comprueba el prototipo de c.

Entonces, creando la propiedad en el la instancia daría lugar a un tiempo de acceso ligeramente más rápido , pero esto solo podría ser un problema para una jerarquía de prototipos muy profunda.

 3
Author: harto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-17 02:31:19

Necesitamos separar la construcción y el uso de los objetos.

Cuando se declara una función en un prototipo, se comparte entre todas las instancias. Cuando se declara una función en un constructor, esto se vuelve a crear cada vez que se crea una nueva instancia. Teniendo en cuenta que, necesitamos comparar la construcción y el uso por separado para tener mejores resultados. Eso es lo que hice y quiero compartir los resultados con ustedes. Este punto de referencia no prueba la velocidad de construcción.

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

De estos resultados podemos ver que la versión prototipo es la más rápida (4ms), pero la versión de cierre es muy cercana (7ms). Es posible que aún necesite un punto de referencia para su caso particular.

Así que:

  • Podemos usar la versión prototipo cuando necesitamos tener cada bit de rendimiento o compartir funciones entre instancias.
  • Podemos usar otras versiones cuando lo que queremos son las características que proporcionan. (encapsulación estatal privada, legibilidad, etc.)

PS: Usé la respuesta de Andrew como referencia. Utiliza los mismos bucles y notación.

 2
Author: Vakho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-17 06:39:16

Hice mis propias pruebas.

La primera conclusión es que el acceso estático es en realidad más lento que el prototipado real. Curiosamente, la Versión 23 de esta prueba tiene un prototipado defectuoso (Variable X), que solo devuelve el objeto prototipo completamente anulado una y otra vez y cuando estaba creando mi prueba, este prototipado era aún más lento que mi prueba de "prototipo real".

De todos modos, a la respuesta : A menos que mi prueba sea defectuosa, muestra que el prototipado real es el más rápido. Bate o es al menos igual al objeto estático al ignorar la instanciación. esto-las asignaciones en instanciación y variables privadas son mucho más lentas. No habría adivinado que las variables privadas serían tan lentas.

Podría ser de interés que extendiera el Objeto prototipo con jQuery.extender en el medio y era aproximadamente la misma velocidad que la asignación directa. La extensión estaba fuera de la prueba en sí, por supuesto. Al menos esta es una manera de eludir escribir molesto".prototipo."- Partes todo el tiempo.

 1
Author: x3c,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-25 15:30:09

Pruebas API de Rendimiento del Navegador de Alta Resolución

Ninguna de las pruebas aquí están aprovechando la API de rendimiento para pruebas de alta resolución, así que escribí una que mostrará los resultados más rápidos actuales para muchos escenarios diferentes, incluyendo 2 que son más rápidos que cualquiera de las otras respuestas en la mayoría de las ejecuciones.

Ayunado en cada categoría (10.000 iteraciones)

  • Solo acceso a la propiedad (~0.5 ms): { __proto__: Type }
  • Creación de objetos en bucle con acceso a la propiedad (: Object.create(Type)

El código utiliza ES6 sin transpilación babel para garantizar la precisión. Funciona en chrome actual. Ejecute la prueba a continuación para ver el desglose.

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
Author: cchamberlain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-29 15:20:01

Estoy seguro de que en cuanto a la instanciación del objeto, es mucho más rápido y también consume menos memoria, no hay dudas al respecto, pero creo que el motor javascript necesita recorrer todas las propiedades del objeto para determinar si la propiedad/método invocado es parte de ese objeto y si no, entonces vaya a buscar el prototipo. No estoy 100% seguro de esto, pero estoy asumiendo que así es como funciona y si es así, entonces en ALGUNOS casos donde su objeto tiene una GRAN CANTIDAD de métodos añadidos a él, instanciado solo una vez y utilizado en gran medida, entonces posiblemente podría ser un poco más lento, pero eso es solo una suposición que no he probado nada.

Pero al final, todavía estaría de acuerdo en que, como reglas generales, usar prototype será más rápido.

 0
Author: SBUJOLD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-16 13:15:15

Por lo tanto, crear la propiedad en la instancia resultaría en un tiempo de acceso ligeramente más rápido, pero esto solo podría ser un problema para una jerarquía de prototipos muy profunda.

En realidad, el resultado es diferente de lo que podríamos esperar: el tiempo de acceso a los métodos prototipados es más rápido que el acceso a los métodos conectados exactamente al objeto (probado FF).

 -3
Author: Piotr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-03 09:58:10