perf impact of passing references

JavaScript performance comparison

Test case created by Dominik Guzei and last updated

Info

This test is attempting to show the performance impact of passing references compared to normal scope chain lookups.

Check out Compact.js, that's based on Require.js

Dominik Guzei Wizzart by the way -> I'm searching for a JavaScript internship :)

Modified By

@jdalton

Preparation code

<script>
  ui.benchmarks[0].setup = function() {
   var Library = {
    'util': {
     'add': function(a, b) {
      return a + b;
     }
    },
    'cool': {
     'Class': function(startValue) {
      this.value = startValue;
     }
    }
   };
 
   Library.cool.Class.prototype = {
    'addToValue': function(value) {
     this.value = Library.util.add(this.value, value);
    }
   };
  };
 
 
  ui.benchmarks[1].setup = function() {
   var cache = {};
 
   function define(namespace, includes, callback) {
    var i = -1;
    if (arguments.length < 3) {
     callback = includes;
     includes = [];
    } else {
     includes = includes.slice(0);
    }
    // convert string namespaces to objects
    while (includes[++i]) {
     includes[i] = cache[includes[i]];
    }
    cache[namespace] = callback.apply(null, includes);
   }
 
   function require(includes, callback) {
    var i = -1;
    includes = includes.slice(0);
    // convert string namespaces to objects
    while (includes[++i]) {
     includes[i] = cache[includes[i]];
    }
    callback.apply(null, includes);
   }
 
   define('Library.util.add', function() {
    function add(a, b) {
     return a + b;
    }
    return add;
   });
 
   define('Library.cool.Class', ['Library.util.add'], function(add) {
    function Class(startValue) {
     this.value = startValue;
    }
    Class.prototype = {
     'addToValue': function(value) {
      this.value = add(this.value, value);
     }
    };
    return Class;
   });
 
   var Klass;
   require(['Library.cool.Class'], function(Class) {
    Klass = Class;
   });
  };
 
 
  ui.benchmarks[2].setup = ui.benchmarks[1].setup;
</script>

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
Normal Lookup
(new Library.cool.Class(1)).addToValue(1);
pending…
Local Lookup
(new Klass(1)).addToValue(1);
pending…
Passing+Require
require(['Library.cool.Class'], function(Class) {
 (new Class(1)).addToValue(1);
});
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

11 comments

Dominik Guzei (revision owner) commented :

This test shows that frameworks built on a global namespace (jQuery, YUI, Prototype, ...) could have been up to 98% faster (Firefox) if they were written in a string based modular fashion like it is used by Compact.js that is based on Require.js

Please tell me wrong, I can't believe it :-) Dominik Guzei Wizzart

Micheil Smith commented :

I think you'll find that it's because you're deep-accessing on objects. If you move the defining of objects outside of the tests, they may well be faster.

Dominik Guzei (revision owner) commented :

Yes it would be faster and that's the whole point: Defining object chains is slow, and accessing them is even worse.

Both snippets to exactly the same, remember that! You can't move the defining of objects out of the test, because then you would be cheating in favor of the first snippet.

Tom Schuster commented :

Lets have a look: eg. window.Library.cool is - load window object - load Library - load cool

you allocate a new object every time with {} already writing var lib = window.Library could already speed up the code vs. - load function - call function - set on closure var f

you are using the same object in the closure

Dominik Guzei (revision owner) commented :

Tom, good points -> I just did some corrections, is it ok now?

Tom Schuster commented :

I did some improvements there: http://jsperf.com/global-namespace-chain-vs-string-based-local-modules/7

Paul Wagland commented :

There is something funny going on for me. Running the test on the main page, I get results of 53/79 testing in Safari 5.0.5 on Mac OS X 10.6.7.

However, when I got to http://jsperf.com/global-namespace-chain-vs-string-based-local-modules/7 then the two tests are essentially the same speed for me, there is a 2-3% difference.

Micheil Smith commented :

Actually, the defining of objects is a negligible difference, the real difference comes from deeply accessing variables. It's long bee known that it's faster to do something like:

for(var i=0, l=foo.length; i < l; ++i) {}

compared to:

for(var i=0; i < foo.length; ++i) {}

as you're not accessing the value for foo.length every time. Likewise, in your add() function, you've got a local reference to add, where as you're accessing three objects deep to get access to the add() function. The first code snippet actually out performs the second if you store a local reference to add(). Also, who actually writes their code using deep references like what you're using above? Most people probably store a few local references purely due to that "programmers are lazy" rule (aka, less typing)

Dominik Guzei (revision owner) commented :

True story :-D I just didn't think the difference would be so extrem!

Thanks for your comments and revisions, just learned a lot from you guys! I also rewrote the placative introduction and (intentionally) didn't change my (crappy) implementation, as a good example how important it is to keep one's locals around!

See you again, in another round :)

Jie commented :

Sorry, I didn't see the comments panel and I commented your test directly on the revision 6! By caching the add function in a closure in the global test, both tests have the same speed.

cdsanchez commented :

I think if someone used the define function they would expect to still have the ability to manually reference that member in the normal notation (i.e. it would define the namespace in the global context). Using this requires you to use the require function if you want to use your modules (the ones created using the define function at least). You could make a case that it doesn't really matter if you're using a dependency manager because you still need to make sure your includes are loaded.. but personally, I'd still like the flexibility to access the modules using the existing standard.

Add a comment