JavaScript Object Access Micro benchmarks

Christian Harms's picture

After getting the tip to use JSLitmus for JavaScript microbenchmarks I grabbed my old "How to access JavaScript Object" code and build some tests with JSLitmus. The following question was sometimes very religious and only benchmarks can solve the problem:

The question is simple: what is faster?

  1. obj = {a:1};
  2.  
  3. //variant 1
  4. result = obj.a;
  5.  
  6. //variant 2
  7. result = obj['a'];
  8.  
  9. //variant 3 - build an object with getter function:
  10. result = obj.getA();

I build some variants and run the benchmarks on different browsers on my Intel Atom N270 eeeBox. The examples show the single function call with JSLitmus for better readability, the benchmarks are made with the function-count-parameter.

Live demo

You can start it here with your browser - some tests will result "Infinity" because the tests can be fast on modern Intel Core cpus. That's one of the reason to use a netbook/netbook with a slow standard Atom cpu. If it run fast on this machine its fast enough for the normal web visitor.

A: Accessing with Dot Notation to a plain Object.

The simple direct access to an JavaScript Object used as key-value data object.

  1. var obj = {a: 1, b: 1, c:1, d:1, e:1};
  2.  
  3. JSLitmus.test("ObjectAccessWithDot", function()
  4.   { sum = obj.a + obj.b + obj.c + obj.d + obj.e; }
  5. );

B: Accessing with Key Notation to a plain Object.

The access with the name as key to an JavaScript Object used as key-value data object. This should be the same like Case B.

  1. var obj = {a: 1, b: 1, c:1, d:1, e:1};
  2.  
  3. JSLitmus.test("ObjectAccessWithKey", function()
  4.   { sum = obj['a'] + obj['b'] + obj['c'] + obj['d'] + obj['e']; }
  5. );

If the key is constant a optimizer can convert the code to the dot notation. The second example use local variables for the keys.

  1. var obj = {a: 1, b: 1, c:1, d:1, e:1};
  2. var a='a', b='b', c='c', d='d', e='e';
  3.  
  4. JSLitmus.test("ObjectAccessWithKey", function()
  5.   { sum = obj[a] + obj[b] + obj[c] + obj[d] + obj[e]; }
  6. );

C/D: Building a constructor, accessing with Dot or Key notation

Plain JavaScript Objects are fine, but we build more complex object and we need contructors. Build an constructor with the five members and get an instance of it for accessing. This should be the same than Case A because type(obj)=="object".

  1. var Data = function() { this.a=1; this.b=1; this.c=1; this.d=1; this.e=1;};
  2. var obj = new Data();
  3.  
  4. JSLitmus.test("ConstructorAccessWithDot", function()
  5.   { sum = obj.a + obj.b + obj.c + obj.d + obj.e; }
  6. );
  7.  
  8. JSLitmus.test("ConstructorAccessWithKey", function()
  9.   { sum = obj['a'] + obj['b'] + obj['c'] + obj['d'] + obj['e']; }
  10. );

E/F: Accessing with getter function to public or private members

The first examples use only direct access to the member variables. Variant E/F use the more java-like getter functions to access to the integers and it should dramaticly slower (function call overhead).

  1. var Data = function() {
  2.      this.a=1; this.b=1; this.c=1; this.d=1; this.e=1;
  3.      this.getA = function() { return this.a; }
  4.      this.getB = function() { return this.b; }
  5.      this.getC = function() { return this.c; }
  6.      this.getD = function() { return this.d; }
  7.      this.getE = function() { return this.e; }
  8.   };
  9. var obj = new Data();
  10.  
  11. JSLitmus.test("testConstructorAccessWithGetter", function()
  12.   { sum = obj.getA() + obj.getB() + obj.getC() + obj.getD() + obj.getE(); }
  13. );

Real private members can be generated in the constructor as local variables. They are only accessable with functions defined in the same variable scope. The benchmark will test if the access to the local scope is more expensive than to public members.

  1. var Data = function() {
  2.      var a=1, b=1, c=1, d=1, e=1;
  3.      this.getA = function() { return a; }
  4.      this.getB = function() { return b; }
  5.      this.getC = function() { return c; }
  6.      this.getD = function() { return d; }
  7.      this.getE = function() { return e; }
  8.   };
  9. var obj = new Data();
  10.  
  11. JSLitmus.test("testConstructorAccessWithPrivateGetter", function()
  12.   { sum = obj.getA() + obj.getB() + obj.getC() + obj.getD() + obj.getE();}
  13. );

G. Accessing members over the prototype chain

The DataProto contructor defines the five member and the Data constructor will inheritance it. While accessing to the member variables it has to hop to the prototype DataProto to get the values. It should be slower than Case A or Case C.

  1. var DataProto = function() {
  2.    this.a=1; this.b=1; this.c=1; this.d=1; this.e=1;
  3. };
  4. var Data = function() {};
  5. Data.prototype = new DataProto();
  6. var obj = new Data();
  7.  
  8. JSLitmus.test("ConstructorAccessWithDot", function()
  9.   { sum = obj.a + obj.b + obj.c + obj.d + obj.e; }
  10. );

The results

Funny! If you access to many members use plain JavaScript objects with dot notation. Charts shows loops/second - higher is better!

benchmark for Chrome 5 on Atom N270benchmark for Firefox 3.5.9 on Atom N270benchmark for Opera 10.10 on Atom N270benchmark für Firefox 3.6 on Atom 270

The Opera 9.80 was a Opera 10.10, the last one is from the Konqueror 4.3.2:
Konqueror 4.3.2 on Atom N270

I dont test the IE because it is not a browser for JavaScript Programmers (ECMA5) and I dont run it on ubuntu ... If any one will post benchmarks for windows (on an netbook with Intel Atom N270) as tinyurl make a comment!

AttachmentSize
objectbench.js.txt4.93 KB

Comments

qFox's picture

The results aren't very surprising. It's a well known fact that function calls are very expensive. Scope lookups (using prototype) are expensive as well (although not as expensive as function calls). A constructor is a function call (but usually even slower).

It wasn't at first clear to me whether more was better. I kind of expected less to be better (and so was a little surprised with the results ;)). But yeah, more is better so that makes more sense. I'm actually still a little surprised that the constructor way turns out to be as fast as it seems to be.

I really like the charts by the way :)

Bruno Garcia's picture

Test A and B are effectively the same to the interpreter, with just a different notation.

However, I found if you use form B with a variable key instead of a string literal, it's much slower (as that can't be JITed as concisely as a compile time property reference).

Oliver's picture

Most JS engines will just convert obj["a"] to obj.a prior to codegen as they are logically equivalent.

Your test should be something akin to
var property = "a";
obj[property];

Of course even a tiny piece of analysis would allow constant folding that would get rid of this cost. That said I don't believe any engine (except maybe O10.5?) does value propagation.

Christian Harms's picture

Thanx Oliver: I added a testcase "ObjectAccessWithKeyVar" and it's correct: Firefox and Chrome optimize the constant key access!

Mirko's picture

Christian Harms's picture

Wow - dot vs. key access looks dramaticly! The graph looks to fast for direct comparision (I edit my call in the article). It's generated on a netbook (Intel Atom)?

Oliver's picture

EcmaScript 5 actually standardises support for getters and setters, eg.

{get a(){ return 1; } }

You should test that. Although it is depressing.

--Oliver

JavaScript Object property getter benchmarks - part 2 | unit's picture

[...] object access benchmarks last weeks was not surprising but a factor 5 between access "obj.a" and "obj.getA()" is [...]

Fredrik Holmström's picture

In a static micro-benchmark like this "obj.a" will always be the fastest due to how most/all modern JavaScript engines work.

In a real world scenario it will never be this easy as most engines switch out the underlying storage mechanism depending on how the object is used most which might make obj["a"] faster then obj.a, or obj[0] faster then obj.a, etc.