Where Should Variables be Declared?

G

Garrett Smith

I've been reading a lot about "variable performance" lately.

In a recent post "Checking accidental global variables", I mentioned
that each variable is property of a Variable object. Which object to add
the variable to should be determined by its role and usage patterns.
Scope of variables should be determined by their role (which is probably
more related to something that is more specific than the global object
itself).

A few days later, I stumbled upon a blog entry by Mike Wilcox:

http://www.sitepen.com/blog/2009/08/10/web-page-global-variable-performance/

The blog entry is discussing variable speed and showed an example of
some messy code (Google Docs and Spreadsheets) that had haphazardly
added a large number identifiers to the global object (many out of
simply forgetting |var|).

My first reaction was that those variables don't belong there! The
Sitepen article was not concerned with design of the code, but was
interested in the performance of variables.

The explanation has some problems, starting with the using the term
"local" to mean "object property" (that's not a local variable!).
Second, test code was not provided. I see some excerpt test code that
does undeclared assignment to identifiers.

Further down, commenter Joel Webber states:-
| Referencing locals via closure is almost always slower than either of
| these two approaches.
| http://blog.j15r.com/2009/08/where-should-i-define-javascript.html

I clicked the link to his blog and found that he was not providing a
test, but providing excerpts from various tests (just like Sitepen) and
the closures test excerpt seemed to be reading global variables (through
a longer scope chain AND accessing variables of the enclosing scope).
That would certainly explain the result of the closure example being
slower. However, Joel's conclusion was: "referencing variables via
closure is almost invariably slower", which did not logically follow the
example code he provided (accessing global variables).

I followed up with that blog entry. Joel did not accept my explanation
of why his conclusion was not proven by his test result. He did publish
a test in a subsequent blog entry, correcting one of the mistakes he had
made previously.

http://blog.j15r.com/2009/09/javascript-variables-continued.html

Great. Actual test code. He corrected one mistake, but the closures
example was reading global properties, just as the excerpt had appeared
to be doing, thereby causing that function to do more work than the
other functions. That could explain the slower performance. I noticed
also the test code file "inc.js" forgets to use var as well:-

| function dump(a) {
| var msg = '';
| for (k in a) {
| msg += k + ': ' + a[k] + '<br>';
| }
| echo(msg);
| }

Oh well.

Forgetting |var| is known to be error-prone in IE. The criticism for
Google docs and spreadsheets is probably related to the same mistake.

A few days later, we had a c.l.js thread "Primitive Re-definition",
which mentioned the practice of storing a shared ("private static")
alias of a global identifier. The example provided showed code that did
essentially:-

(function(){
var undefined;
// code that uses local undefined.
})();

The purported benefits:
1) identifier is "mungeable", resulting in smaller file size.
2) identifier resolution is shorter, thereby improving performance.

The first argument is obviously true, is easily provable, and a few
subsequent commenters pointed this out.

The second argument is that identifier resolution "scope chain" is
shorter, and that this should improve performance. This was also
mentioned in the Sitepen article. What this means is that in the nested
function "isUndefined()", the resolution of identifier |undefined| is:
[[Scope]] -> [[Scope]]. If the outer function had not declared |var
undefined;| then the inner resolution would have been: [[Scope]] ->
[[Scope]] -> [[Scope]], resolving |undefined| on window.

This should, in theory, be faster. Joel Webber stated that it is not,
but the test provided does not prove that.

I wrote a test to compare performance of the functions that:-
1) assign to global variable
2) read global variable
3) assign to local variable
4) read local variable:
(requires first creating, then reading back).
5) assign to variable in enclosing function scope
6) reads value of var in enclosing scope
7) assign to global variable from nested scope
8) assign to global var from nested scope.
9) read global var from nested scope (part of Joel Webber's "closures"
test).
10) set property of object in
11) create a new object with properties

The purpose of tests 10 and 11 is to compare the speed of creating a new
object with reusing an existing one. Reusing an object will result in
the creation of fewer objects, so this should require less memory. Will
it be any faster?

The test generates functions in an iframe writes a script tag containing
the 11 functions described above. Each function contains 14000
assignment and/or read expressions.

Each test is run 150 times, and is run after a setTimeout of 1100ms. The
setTimeout is for two reasons:-
1) allow garbage collector to run between tests. End tests should not be
affected by the garbage collector running after prior tests.
2) avoid slow script dialog.

The tests were run on
1) Windows Vista Ultimate on a 2ghz Intel Duo
2) Mac G5 2ghz Dual Processor (non Intel)

Results:-
http://dhtmlkitchen.com/jstest/variable-performance.html

Analysis:
Accessing a variable in a containing function scope (closure) is faster
in the tested browsers excepting:
* Firefox 3.5:
Tracemonkey was slower with closures.
* IE7:
Setting then accessing a local variable was faster than accessing
the variable from the closure.
* Accessing a variable /through/ an augmented scope chain is slower in
all browsers.
* Reading a local variable is fast, however the variable must be
assigned on each call to that function. Depending on the situation,
assigning and reading back a variable can be wasteful of resources and
may be slower. The performance depends the time it takes to evaluate the
AssignmentExpression for that variable, and this varies between browsers.

Safari 2 was by far the slowest browser tested. Chrome 2 is "holy
closures" fast ;-D.

Performance varies between implementations.

Applying the results:
Short answer:
* don't forget to use var.
* don't use global variables to boost performance (it doesn't).

Putting all identifiers as globals won't help performance.

Resolving a global identifier takes longer than resolving a property of
a local variable, and (except for Firefox) closures are generally
faster. Justification that global identifiers should be used for
performance reason is an unjustified excuse for poor program design.

Adding an identifier to an object (the global object) without
considering how (or if) the role of the identifier is related to that
object, or if the identifier should be globally accessible is a good way
to write confusing code.

Identifiers that need to be shared across a group of functions should be
stored in a closure (private static). This makes the most sense from a
design perspective because it simplifies the object's public interface.
The added bonuses is that doing this result in better performance in all
but Firefox 3.5, and will result in smaller minified file size due to
munged identifiers.

Chrome is blindingly fast on closure access. That shows that an
implementation optimization is possible. Other browsers should make such
optimizations in future releases.

Accessing properties of a shared Object will be somewhat fast. Accessing
local variables will also be fast, however local variables can't be
shared or reused unless they are passed around. If a variable is reused,
especially if the value of that variable is an object, then recreating
it each time is memory-inefficient. Storing it in the containing scope
will probably be a better alternative.
 
G

Garrett Smith

Garrett said:
I've been reading a lot about "variable performance" lately.
[...]

I wrote a test to compare performance of the functions that:-
1) assign to global variable
2) read global variable
3) assign to local variable
4) read local variable:
(requires first creating, then reading back).
5) assign to variable in enclosing function scope
6) reads value of var in enclosing scope
7) assign to global variable from nested scope
8) assign to global var from nested scope.
9) read global var from nested scope (part of Joel Webber's "closures"
test).
10) set property of object in
11) create a new object with properties

The test is linked here:
http://dhtmlkitchen.com/jstest/scope-chain-performance-iframe.html
 
M

Matt Kruse

Chrome is blindingly fast on closure access. That shows that an
implementation optimization is possible. Other browsers should make such
optimizations in future releases.

Given the extremely small performance increases in these techniques, I
think it's best to _not_ try to prematurely optimize, and instead let
browsers do that for you. Write code that makes sense
programmatically, and allow js engines to come up with the best way to
speed it up.

Attempting to optimize by messing with identifier resolution and scope
chains makes assumptions about browser implementation that may not be
true, and may in fact break optimizations that would have resulted in
even _faster_ performance had you not tried to mess with it!

Good write-up!

Matt Kruse
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,049
Latest member
Allen00Reed

Latest Threads

Top