Agata
Agata

Reputation: 554

Performance of accessing properties - Factory function vs. Concatenative inheritance vs. Class

please help :) — I had to check performance of accessing properties — Factory function, Concatenative inheritance and class and I don’t understand why Concatenative inheritance loses? After all, access to the copied properties in Concatenative inheritance should be the fastest.  I run test on Chrome and Safari and CI always loses. https://jsperf.com/factory-function-vs-concatenative-inheritance-get

Code from this test:

const alien = {
    sayHello () {
      return `Hello, my name is ${ this.name }`;
    }
  };
  //Delegate prototype
  const createAlienFF = (name) => { 
     return Object.assign(Object.create(alien), {
       name
    });
  }
   //Delegate prototype - creating objects
  var tabFF = [];
  for(var i = 0; i < 1000; i++ ) {
     tabFF[i] = createAlienFF('Clark' + i);
  }

  //Concatenative inheritance
  const createAlienCI = (name) => { 
     return Object.assign( 
      {}, 
      alien, 
      {name: name}
    );
  }
  //Concatenative inheritance - creating objects
  var tabCI = [];
  for(var i = 0; i < 1000; i++ ) {
     tabCI[i] =  createAlienCI("Clark" + i );
  }

  //Class
  class Alien {
    constructor(name) {
      this.name = name
    }
    sayHello() {
      return `Hello, my name is ${ this.name }`;
    }
  }
  //Class - creating objects
  var tabClass = [];
  for(var i = 0; i < 1000; i++ ) {
     tabClass[i] = new Alien("Clark" + i );
  }
  //Tests
  //1 - Delegate prototype
  for(var i = 0; i < 1000; i++ ) {
    tabFF[i].sayHello();
  }

 //2 - Concatenative inheritance
 for(var i = 0; i < 1000; i++ ) {
  tabCI[i].sayHello();
 }

//3 - Class
for(var i = 0; i < 1000; i++ ) {
  tabClass[i].sayHello();
}

Upvotes: 2

Views: 271

Answers (1)

Bergi
Bergi

Reputation: 664548

After all, access to the copied properties in Concatenative inheritance should be the fastest.

Why do you think that? Because it's a direct property access that does not need to traverse the prototype chain?

Well yeah, that might be a good reasoning about a naive engine implementation. But you didn't reckon with the optimisations of the object model that is specifically tailored towards inheritance, making method access on (class) instances blazingly fast. Have a look at https://github.com/v8/v8/wiki/Design%20Elements#fast-property-access, https://blog.ghaiklor.com/optimizations-tricks-in-v8-d284b6c8b183, http://richardartoul.github.io/jekyll/update/2015/04/26/hidden-classes.html and the great http://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html. The gist: V8 optimises the method access on objects of "known shape", that is the objects inheriting from alien or Alien.prototype in your case (they share the same hidden class). It knows guesses that in your loop, only objects of that shape are used, and can deduce that all of them call the exact same sayHello function. That's a constant location to fetch the code from, enabling even further optimisations such as inlining. On the other hand, the objects created by the createAlienCI factory do as well all share the same shape, but each object contains its individual sayHello property. All these properties might contain the same function object, but we cannot know that (and we wouldn't guess it - it's an unusual pattern). The engine therefore fetches the function reference from each instance every time before calling it - at least it knows where (at which memory offset) in each instance to look because of their constant shape.

Upvotes: 1

Related Questions