53 votes

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

Je lis ici (Douglas Crockford) utilisation de l'opérateur prototype pour ajouter des méthodes aux classes Javascript économise également de la mémoire .

Puis j'ai lu dans cet article de John Resig "L'instanciation d'une fonction avec un ensemble de propriétés prototypiques est très, très, rapide " Mais parle-t-il de l'utilisation d'un prototype de la manière habituelle, ou de l'exemple spécifique qu'il donne dans son article ?

Par exemple, la création de 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 l'objet singleton, etc. Mais cette question n'a rien à voir avec ces sujets.


EDIT : pour ceux que cela pourrait intéresser également comparaison des performances entre un objet JS et un objet statique JS peut lire cette réponse ci-dessous . Les objets statiques sont incontestablement plus rapides mais il est évident qu'ils ne peuvent être utilisés que lorsque vous n'avez pas besoin de plus d'une instance de l'objet.

3 votes

Personnaliser un objet en lui ajoutant des propriétés revient à traiter un cas particulier pour cet objet. Cela prend plus de mémoire et est plus lent, l'utilisation du prototype demande explicitement au "compilateur" de pointer sur la définition de la classe, en partageant le même espace mémoire. Renvoyer un objet explicite au lieu de le construire à partir d'une méthode peut également aider le compilateur à optimiser le code (même si ce n'est pas toujours possible). Plus d'informations ici à partir de la diapositive 7

0 votes

Javascript n'a pas de classes...

66voto

Andrew Points 5374

Editer en 2021 :

Cette question a été posée en 2010 lorsque class n'était pas disponible en JS. Aujourd'hui, class a été tellement optimisé qu'il n'y a plus d'excuse pour ne pas l'utiliser. Si vous devez utiliser new , utiliser class . Mais en 2010, vous aviez deux options pour lier les méthodes à leurs constructeurs d'objets : l'une consistait à lier les fonctions à l'intérieur du constructeur de fonction en utilisant la fonction this et l'autre consistait à les lier à l'extérieur du constructeur à l'aide de prototype . La question de @MarcoDemaio comporte des exemples très concis. Quand class a été ajouté à JS, les premières implémentations étaient proches en termes de performances, mais généralement plus lentes. Ce n'est plus du tout le cas aujourd'hui. Il suffit d'utiliser class . Je ne vois aucune raison d'utiliser prototype aujourd'hui.


La question était intéressante, j'ai donc effectué quelques 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 cela vaut). Il semble qu'au moins sur Safari et Firefox, prototype fonctionne nettement plus vite [edit : pas 20x comme indiqué précédemment]. Je suis sûr qu'un test réel avec des objets complets serait une meilleure comparaison. Le code que j'ai exécuté était le suivant (j'ai exécuté 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 < 10000000; 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 < 10000000; 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, parce que je déteste vraiment utiliser prototype . J'aime que mon code objet soit auto-encapsulé et qu'il ne puisse pas dériver. Je suppose que lorsque la vitesse est importante, je n'ai pas le choix. Mince

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

0 votes

Je peux également vérifier que ce benchmark semble correct Bien que je sois curieux du faible nombre de Safari. Quelle version utilisez-vous ? Sur ma machine, je vois FireFox 3.6.8 : 5030/377, Safari 5.0 : 3037/264.

0 votes

Il serait également intéressant de voir la répartition du temps - c'est-à-dire le temps nécessaire pour instancier chacun des éléments de l X y Y le temps nécessaire à la recherche d'un bien (ex, var tmp = x.message; ) et le temps nécessaire pour appeler un bien (ex, x.message('hi') ).

0 votes

@David Wolever - Mon Safari est 5.0.6553.16, qui a été poussé assez récemment. Je suis sur un Mac. Je ne sais pas trop pourquoi je n'utilise pas Safari plus souvent. Il est ridiculement rapide et le débogueur est largement supérieur à Firebug, mais stupidement je ne l'utilise jamais, sauf quand je dois le faire. J'ai essayé ce que vous avez suggéré. Une simple recherche de propriété ajoute environ 10% par rapport à l'instanciation seule. Les deux méthodes ont montré à peu près la même augmentation. Il ne semble donc pas y avoir de coût ou d'économie supplémentaire à utiliser l'une ou l'autre méthode. Cela m'a un peu surpris. Encore une fois, il serait préférable d'essayer ceci avec un exemple réel.

33voto

shmuel613 Points 838

Je pense 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 l'a emporté 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 :

Lien

Résultats :

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

IE8 : temps de fermeture : 781, temps de prototype : 406, temps de l'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 à tirer est que si vous NE PAS Si vous avez besoin d'instancier de nombreux objets différents à partir de la même classe, la création d'un objet statique l'emporte haut la main. Réfléchissez donc bien au type de classe dont vous avez réellement besoin.

0 votes

Au moins, un +1 est dû ici ! Merci d'avoir partagé votre réflexion et le test !!! J'utilise couramment static object en JS (et souvent aussi dans d'autres langages, plutôt que d'utiliser un Singleton) quand je n'ai pas besoin d'instancier plus d'une instance d'un objet. Je m'attendais à ce que static object soit plus rapide, mais je suis content que vous l'ayez clarifié ici avec un test. Merci encore !

0 votes

Joli test, qui répond à ma question sur la conception de Javascript !

1 votes

J'ai ajouté un test à jsperf.com si cela intéresse quelqu'un : jsperf.com/fermeture-prototype-statique-performance

6voto

user1822264 Points 61

J'ai donc décidé de tester cela aussi. 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 sur un Mac Book Pro démarré sous Windows 7. Les résultats "rapides" utilisent des prototypes et les résultats "lents" utilisent des modules. J'ai créé 1 million de chaque type d'objet et 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 le suivant :

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 : Ces résultats confirment ceux obtenus par d'autres personnes dans le cadre de cet article. Si vous créez constamment des objets, le mécanisme du prototype est clairement plus rapide. Si votre code passe le plus clair de son temps à accéder à des objets, alors le modèle du 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.

0 votes

Je ne suis pas un sensei JavaScript mais j'ai créé ce test pour tester les performances lors de l'accès et le voici : jsperf.com/accessing-prototyped-and-static-objects

1 votes

En 360 bytes n'est pas fixe : elle dépend du nombre de propriétés que vous définissez et de leurs valeurs, ainsi que des éventuelles optimisations effectuées par le moteur.

2voto

harto Points 28479

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

Cependant, il y aura une légère différence de performance lorsqu'il s'agira de accès la fonction. Quand c.showMsg est référencée, le moteur d'exécution JavaScript vérifie d'abord la propriété sur c . S'il n'est pas trouvé, c est ensuite vérifié.

Ainsi, la création de la propriété sur l'instance se traduirait par un temps d'accès légèrement plus rapide, mais ce problème ne se poserait que dans le cas d'une hiérarchie de prototypes très profonde.

0voto

SBUJOLD Points 1071

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, sans aucun doute, mais je pense que le moteur javascript doit faire une boucle à travers toutes les propriétés de l'objet pour déterminer si la propriété/méthode invoquée fait partie de cet objet et, si ce n'est pas le cas, il doit vérifier le prototype. Je ne suis pas sûr à 100% mais je suppose que c'est ainsi que cela 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 intensivement, alors cela pourrait être un peu plus lent, mais ce n'est qu'une supposition, je n'ai rien testé du tout.

Mais en fin de compte, je suis toujours d'accord pour dire qu'en règle générale, l'utilisation d'un prototype est plus rapide.

0 votes

Il y aura une certaine surcharge pour accéder aux propriétés dans le prototype, mais la plupart des moteurs utilisent une table de hachage, de sorte que l'ajout d'un grand nombre de propriétés n'a pas d'importance. Je pense qu'IE est l'exception, et qu'il a besoin de faire une boucle à travers chaque propriété.

0 votes

Oui, en écrivant cela, je me suis dit qu'il devait y avoir des optimisations qui ont été faites au fil des années... donc je suppose que cela ajoute au fait que l'ajout de méthodes/propétences à l'objet prend du temps parce que la carte/table doit être reconstruite/mise à jour ?

0 votes

Oui, l'ajout d'un grand nombre de propriétés entraînerait la reconstruction de la table.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X