Performance Execution on JS Objects Created in Different Ways

Posted on Jan 26, 2015

In JS (javascript) there are different approaches to create objects which differ in somehow not only syntax but in small things that you can achieve.

My aim in this post is not to talk of those different approaches in the sense how they differ in terms of encapsulation, code readability, programming paradigm approach, best practices, likes and preferences; I think that there are enough posts, articles, resources, etc, with full details, well explained and good quality about this topic on Internet.

I’m going to show some basic performance tests that I’ve done with an object created with those different approaches; just to have fund and because I was curious about it.

Test implementations

I’ve implemented a silly example of those different approaches and after I executed a performance test in different platforms (Browsers, Node and iojs); let’s see first the used object creation approaches implementations:

  • Usual Constructor Function
function ContentPrototype() {
  this.text = null;
}

ContentPrototype.prototype.set = function (text) {
  if (typeof text !== 'string') {
      throw new Error('Content mus be a string');
  }

  this.text = text;
};

ContentPrototype.prototype.get = function (text) {
  return this.text;
};
  • Usual Constructor Function without Object Prototype
function ContentPrototypeNull() {
  this.text = null;
}

ContentPrototypeNull.prototype.__proto__ = null;
ContentPrototypeNull.prototype.set = function (text) {
  if (typeof text !== 'string') {
      throw new Error('Content mus be a string');
  }

  this.text = text;
};

ContentPrototypeNull.prototype.get = function (text) {
  return this.text;
};
  • Constructor Function which uses Object.defineProperty
function ContentProperty() {
  var text = null;

  Object.defineProperty(this, 'content', {
    set: function (c) {
      if (typeof c !== 'string') {
          throw new Error('Content mus be a string');
      }

      text = c;
    },
    get: function () {
      return text;
    }
  });
}
  • Constructor Function which uses Object.defineProperty without Object Prototype
function ContentPropertyNull() {
  var text = null;

  Object.defineProperty(this, 'content', {
    set: function (c) {
      if (typeof c !== 'string') {
          throw new Error('Content mus be a string');
      }

      text = c;
    },
    get: function () {
      return text;
    }
  });
}

ContentPropertyNull.prototype.__proto__ = null;
  • Function which returns an Usual Object Literal
function contentFunction() {
  var text = null;

  return {
      set: function (c) {
        if (typeof c !== 'string') {
            throw new Error('Content mus be a string');
        }

        text = c;
      },
      get: function () {
        return text;
      }
  };
}
  • Function which returns an Object Literal without Object Prototype
function contentFunctionNull() {
  var text = null;
  var obj = Object.create(null);

  obj.set = function (c) {
    if (typeof c !== 'string') {
      throw new Error('Content mus be a string');
    }

    text = c;
  };

  obj.get = function () {
    return text;
  };

  return obj;
}
  • Function which returns an Object created by Object.create defining properties
function contentFunctionObjCreate() {
  var text = null;

  return Object.create(Object, {
    content: {
      set: function (c) {
        if (typeof c !== 'string') {
          throw new Error('Content mus be a string');
        }

        text = c;
      },
      get: function () {
        return text;
      }
    }
  });
}
  • Function which returns an Object created by Object.create defining properties without Object Prototype
function contentFunctionObjCreateNull() {
  var text = null;

  return Object.create(null, {
    content: {
      set: function (c) {
        if (typeof c !== 'string') {
          throw new Error('Content mus be a string');
        }

        text = c;
      },
      get: function () {
        return text;
      }
    }
  });
}

After I wrote those examples, I also wrote little test for each one to check that they have the same behaviour in both operations (read and write), however I haven’t added here to avoid noise.

Performance Execution Tests

Thereafter I’ve created two performance test scripts to run in Node and iojs in my laptop (8 cores):

  1. My own simple implementation which basically execute each operation a bunch of times in a loop, stores the individual values and calculates the time execution average; you can find it in this gist
  2. Another one which uses benchmark.js with microtime whose script you can find in this gist

In the case of the browsers, I’ve used jsperf.

Then I’ve executed the performance execution test in the mentioned platforms to see how the objects, created with each different approach, perform in terms of writing and reading an object’s property value.

iojs

I used the last iojs version available so far, which is 1.1.0.

My own script returned these results

Benchmarjs script returned these ones

Node

I used the last production Node version available so far, which is 0.12.0, YES v0.12 has been finally released.

My own script returned these results

Benchmarjs script returned these ones

Browser

In the browser you can find two tests, which are the same than the iojs and Node ones, but I had to split them in two, one to run the writing property operation and another for the reading one, to offer better results readability.

I’ve run each one in three browsers, Chrome, Firefox and Safari and two different versions in the case of Chrome, one of them the current Chrome Canary; however I encourage you to run with your browsers to have more samples of the same borwser/version and/or new ones from different browsers/versions.

The writing property value test showed me the next chart which I took an screenshot

The reading property value test and its screenshot that I took

My Analysis

Due that Node and iojs benchmark don’t make more difference between running the benchmark test asynchronous and synchronous, I’ve just needed to take one of them in both cases to compare.

Having the comparison of writing and reading an object property value between them using benchmark script is not quite clear about the differences, more or less they perform similar and one is a little bit faster than other in some cases and vice versa.

If I have to asses them using my own script, I can see that overall, Node performs around 5% faster than iojs.

However both are consistent in what create object approaches perform faster in terms of writing and reading values.

We can see that Node and iojs, writes and reads perform better with objects whose properties have been defined using Object.defineProperty or defining them through Object.create; the approach that performs worse is any kind of constructors and in the middle we have the object literals; we cannot see much difference between inherit from Object prototype than without.

Having the difference between the best approach and the worst one we can get gains around 40% in writing and 200% in reading, so not bad to have thought.

In the case of the browsers, we’ve got that the performance between the different approaches is the same for writing and reading.

Between them, Firefox and Safari perform very good with object literals; Firefox does, in second place with constructors and so bad with objects created with Object.create and/or having the properties set by Object.defineProperty and Safari the opposite.

Surprisingly Chrome performs equally with all of them less object literals, and Chrome Canary increases those with constructors, maintains the same with object literals and decrease so much with objects created by Object.create which define their properties with it.

In the browser, you can see easily, thanks to the charts showed by jsperf, the roughly percentage of gain between approaches and browsers.

Conclusion

Having these result you can decide what approach to use in your implementations to boost the performance, however you may only get a benefit of it if you are writing and reading values on objects properties that you’ve created their implementation; nonetheless I only assessed CPU so, profiling the amount of memory spend with the different approaches when you have to create a lot of them, would also be important to keep the best balance.

Moreover in the time being the results are those, but mostly, browsers are updated fast and these results may change in the near future.

From my point of view, it isn’t worthwhile to focus in performance and forget code readability, programming paradigm and best practices to keep the implementation simple and clear to ease the maintainability; hence after I’ve written it, I suggest to read these two rules of the Unix philosophy:

  1. Rule of Clarity: Clarity is better than cleverness
  2. Rule of Optimization: Prototype before polishing. Get it working before you optimize it

I hope that you’ve enjoyed.