web-dev-qa-db-fra.com

Performances de l'opérateur du prototype Javascript: économise de la mémoire, mais est-ce plus rapide?

J'ai lu ici (Douglas Crockford) en utilisant l'opérateur prototype pour ajouter des méthodes aux classes Javascript économise également la mémoire .

Puis j'ai lu dans cet article de John Resig "L'instanciation d'une fonction avec un tas de propriétés de prototype est très, très, rapide ", mais parle-t-il d'utiliser le prototype de manière standard, ou parle-t-il de son exemple spécifique dans son article?

Par exemple, crée cet objet:

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

plus lent que la création de cet objet, alors?

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

P.S.

Je sais que le prototype est utilisé pour créer l'héritage et un objet singleton, etc. Mais cette question n'a rien à voir avec ces sujets.


EDIT: à qui il pourrait également être intéressé par la comparaison des performances entre un objet JS et un objet statique JS peut lire cette réponse ci-dessous =. Les objets statiques sont nettement plus rapides , évidemment ils ne peuvent être utilisés que lorsque vous n'avez pas besoin de plus d'une instance de l'objet.

50
Marco Demaio

C'était une question intéressante, j'ai donc effectué des tests très simples (j'aurais dû redémarrer mes navigateurs pour vider la mémoire, mais je ne l'ai pas fait; prenez cela pour ce que ça vaut). Il ressemble au moins à Safari et Firefox, prototype s'exécute beaucoup plus rapidement [edit: pas 20x comme indiqué précédemment]. Je suis sûr qu'un test du monde réel avec des objets complets serait une meilleure comparaison. Le code que j'ai exécuté était le suivant (j'ai effectué les tests plusieurs fois, séparément):

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

C'est vraiment dommage, car je déteste vraiment utiliser prototype. J'aime que mon code objet soit auto-encapsulé et ne puisse pas dériver. Je suppose que lorsque la vitesse compte, je n'ai pas le choix. Zut.

[Edit] Un grand merci à @Kevin qui a souligné que mon code précédent était erroné, donnant un coup de pouce énorme à la vitesse signalée de la méthode prototype. Après la réparation, le prototype est toujours beaucoup plus rapide, mais la différence n'est pas aussi énorme.

60
Andrew

Je suppose que cela dépend du type d'objet que vous souhaitez créer. J'ai effectué un test similaire à celui d'Andrew, mais avec un objet statique, et l'objet statique a gagné haut la main. Voici le test:

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

Ce test est une modification du code que j'ai trouvé sur:

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

Résultats:

IE6: heure de fermeture: 1062, heure du prototype: 766, heure d'objet statique: 406

IE8: temps de fermeture: 781, temps de prototype: 406, temps d'objet statique: 188

FF: temps de fermeture: 233, temps de prototype: 141, temps d'objet statique: 94

Safari: temps de fermeture: 152, temps de prototype: 12, temps d'objet statique: 6

Chrome: temps de fermeture: 13, temps de prototype: 8, temps d'objet statique: 3

La leçon apprise est que si vous NE PAS avoir besoin d'instancier de nombreux objets différents de la même classe, puis en le créant comme un objet statique gagne la main vers le bas. Réfléchissez donc bien au type de cours dont vous avez vraiment besoin.

31
shmuel613

J'ai donc décidé de tester cela également. J'ai testé le temps de création, le temps d'exécution et l'utilisation de la mémoire. J'ai utilisé Nodejs v0.8.12 et le framework de test mocha fonctionnant sur un Mac Book Pro démarré sous Windows 7. Les résultats "rapides" utilisent des prototypes et ceux "lents" utilisent un modèle de module. J'ai créé 1 million de chaque type d'objet, puis j'ai accédé aux 4 méthodes de chaque objet. Voici les résultats:

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)

Le code est comme suit:

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

Conclusion: Cela confirme ce que les autres dans ce post ont trouvé. Si vous créez constamment des objets, le mécanisme du prototype est clairement plus rapide. Si votre code passe la plupart de son temps à accéder aux objets, le modèle de module est plus rapide. Si vous êtes sensible à l'utilisation de la mémoire, le mécanisme du prototype utilise ~ 360 octets de moins par objet.

6
user1822264

Intuitivement, il semble qu'il serait plus efficace en mémoire et plus rapide de créer des fonctions sur le prototype: la fonction n'est créée qu'une seule fois, pas chaque fois qu'une nouvelle instance est créée.

Cependant, il y aura une légère différence de performances quand il sera temps de accéder à la fonction. Quand c.showMsg est référencé, le moteur d'exécution JavaScript vérifie d'abord la propriété sur c. S'il n'est pas trouvé, le prototype de c est alors vérifié.

Ainsi, la création de la propriété sur l'instance entraînerait un temps d'accès légèrement plus rapide - mais cela ne pourrait être un problème que pour une hiérarchie de prototypes très approfondie.

3
harto

Nous devons séparer la construction et l'utilisation des objets.

Lors de la déclaration d'une fonction sur un prototype, elle est partagée entre toutes les instances. Lors de la déclaration d'une fonction dans un constructeur, celle-ci est recréée chaque fois qu'une nouvelle instance est créée. Compte tenu de cela, nous devons comparer la construction et l'utilisation séparément pour obtenir de meilleurs résultats. C'est ce que j'ai fait et je veux partager les résultats avec vous. Cette référence ne teste pas la vitesse de construction.

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

D'après ces résultats, nous pouvons voir que la version prototype est la plus rapide (4 ms), mais la version de fermeture est très proche (7 ms). Vous devrez peut-être encore comparer votre cas particulier.

Alors:

  • Nous pouvons utiliser la version prototype lorsque nous avons besoin d'avoir chaque bit de performance ou de partager des fonctions entre les instances.
  • Nous pouvons utiliser d'autres versions lorsque ce que nous voulons, ce sont les fonctionnalités qu'elles offrent. (encapsulation par l'État privé, lisibilité, etc.)

PS: j'ai utilisé la réponse d'Andrew comme référence. J'ai utilisé les mêmes boucles et la même notation.

2
Vakhtang

Tests d'API de performances de navigateur haute résolution

Aucun des tests ici ne profite de API de performance pour les tests à haute résolution, donc j'en ai écrit un qui montrera les résultats les plus rapides actuels pour de nombreux scénarios différents, y compris 2 qui sont plus rapides que toutes les autres réponses sur la plupart des s'exécute.

À jeun dans chaque catégorie (10 000 itérations)

  • Accès à la propriété uniquement (~ 0,5 ms): { __proto__: Type }
  • Création d'objet en boucle avec accès à la propriété (<3ms): Object.create(Type)

Le code utilise ES6 sans transpilation babel pour garantir la précision. Il fonctionne dans le chrome actuel. Exécutez le test ci-dessous pour voir la répartition.

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

j'ai exécuté mes propres tests .

La première conclusion est que l'accès statique est en fait plus lent que le vrai prototypage. Fait intéressant, la version 23 de ce test a un prototype défectueux (Variable X), qui renvoie simplement l'objet prototype complètement surchargé encore et encore et lorsque je créais mon test, ce prototypage était encore plus lent que mon "vrai prototype".

Quoi qu'il en soit, à la réponse : à moins que mon test soit défectueux, cela montre que le vrai prototypage est le plus rapide. Il bat ou est au moins égal à l'objet statique en ignorant l'instanciation. ces affectations sur l'instanciation et les variables privées sont toutes deux beaucoup plus lentes. Je n'aurais pas deviné que les variables privées seraient aussi lentes.

Il pourrait être intéressant que j'étende le prototype de l'objet avec jQuery.extend entre les deux et c'était à peu près la même vitesse que l'affectation directe. L'extension était en dehors du test lui-même, bien sûr. Au moins, c'est une façon de contourner l'écriture ennuyeuse de ".prototype". - Des pièces tout le temps.

1
x3c

Je suis sûr qu'en ce qui concerne l'instanciation de l'objet, c'est beaucoup plus rapide et consomme également moins de mémoire, cela ne fait aucun doute, mais je pense que le moteur javascript doit parcourir toutes les propriétés de l'objet pour déterminer si la propriété/La méthode invoquée fait partie de cet objet et sinon, allez vérifier le prototype. Je ne suis pas sûr à 100% de cela, mais je suppose que c'est comme ça que ça fonctionne et si c'est le cas, alors dans CERTAINS cas où votre objet a BEAUCOUP de méthodes ajoutées, instanciées une seule fois et utilisées massivement, alors cela pourrait éventuellement être un un peu plus lentement, mais c'est juste une supposition que je n'ai rien testé.

Mais au final, je serais quand même d'accord pour dire qu'en règle générale, l'utilisation du prototype sera plus rapide.

0
SBUJOLD