No one should care about JavaScript performance. But if you do, this page will help you get a feel for which operations are fast and which are slow. For example, you might be surprised to find that accessing array elements is no faster than accessing object properties.
How to play. Double-click any box in the “Results” column to run the test. Or click the “Start tests” button to run them all; but that takes a while. Each test takes 2 or 3 seconds to run.
Details. How does the tester work? Why is the code so convoluted? Can I depend on the results for my nuclear or medical application? All is revealed below.
Internet Explorer troubleshooting. In IE, some
tests trigger the infamous “A script on this page is making
Internet Explorer run slowly” popup. This makes the test
results useless. Fortunately there's a workaround, discovered by Tony Mills.
Just tweak the MaxScriptStatements
registry setting and IE will totally chill. Tony writes:
“The KB entry has an error though. The key is actually
‘Internet Explorer’ not
‘InternetExplorer’. ...By the way, further
testing revealed that setting the limit to 0 doesn't remove the
message, it just makes IE really annoying. :) So
0xFFFFFFFF is the best value to use.”
| Test | Code | Result |
|---|---|---|
| Empty loop | (none) |
These tests compare the performance of local, global, and closure variables.
| Create local variable | var x; |
|
| Create 10 local variables | var x1, x2, x3, x4, x5, x6, x7, x8, x9, x10; |
|
| Create and initialize local variable | var x = 13; |
|
| Increment local | var i = 0; %% i++; |
|
| Access local | var _test_value = 123; %% _test_value; |
|
| Access global | window._test_value = 123; %% _test_value; |
|
| Access variable in closure | function makeFn(n) {
return function() {
var nTests = _n;
for (var i = 0; i < nTests; i++)
n;
}
};
var runTest = makeFn(123);
%%
runTest();
break;
|
|
| Assign local | var _test_value; %% _test_value = 123; |
|
| Assign global | window._test_value = 123; %% _test_value = 123; |
|
| Assign to variable in closure | function makeFn(n) {
return function() {
var nTests = _n;
for (var i = 0; i < nTests; i++)
n = 123;
}
};
var runTest = makeFn(123);
%%
runTest();
break;
|
On my machine, function calls are several times faster in Mozilla than in IE.
Calling a global function is slower than calling a local function, but only because of the name lookup.
In IE, the last test here consistently runs about 5% faster than the very similar one immediately preceding it. No idea why.
| Call locally defined function | function f() {}
%%
f(); |
|
| Call locally created anonymous function | var f1 = function () {};
%%
f1(); |
|
| Call global function | window.f1 = new Function("");
%%
f1(); |
|
Call function created with new Function |
var f5 = new Function ("");
%%
f5(); |
|
| Call global function via local alias | window.f6 = new Function("");
var f6local = f6;
%%
f6local(); |
In Mozilla, a function expression without a name runs twice as fast as one with a name.
In IE, defining a local function takes no time at all. The local function is created as soon as the function is called, and it is not re-created each time the test loop runs. So the test loop is essentially empty. In Mozilla, a new function object is created each time through the loop.
A call to new Function is slower than a function
expression, presumably because it has to compile the code.
| Define local function | function f() {} |
|
| Create function using function expression without name | var f;
%%
f = function () {}; |
|
| Create function using function expression with name | var f;
%%
f = function jane() {}; |
|
Create function using Function constructor |
var f;
%%
f = new Function(""); |
|
| Create nonempty function | var f;
%%
f = function elementText(elt) {
var a = [];
for (var n = elt.firstChild;
n !== null;
n = n.nextSibling)
{
if (n.nodeType == 3 || n.nodeType == 4)
a.push(n.nodeValue);
}
return a.join("");
};
|
|
| Create deeply nested function | var a = ["var myfn = function () { "];
for (var i = 0; i < 500; i++)
a.push("return function () { ");
a.push("return null;");
for (var i = 0; i < 500; i++)
a.push(" };");
a.push("}; myfn");
var makeFn = eval(a.join(""));
var f;
%%
f = makeFn();
|
| Create empty array | ([]); |
|
| Create small array | ([1, 2, 3]); |
|
| Create small array and local variable | var a = [1, 2, 3]; |
|
| Access elements by index | var a = [1, 2, 3, 4, 5]; %% a[3] |
|
| Populate 10-element array using element assignment | var a; %% a = []; for (var i = 0; i < 10; i++) a[i] = i; |
|
Populate 10-element array using push() |
var a; %% a = []; for (var i = 0; i < 10; i++) a.push(i); |
|
| Populate 1000-element array of numbers using element assignment | var a; %% a = []; for (var i = 0; i < 1000; i++) a[i] = i; |
|
Populate 1000-element array of numbers using push() |
var a; %% a = []; for (var i = 0; i < 1000; i++) a.push(i); |
|
Populate 1000-element array of objects using push() |
var a;
var x = {};
%%
a = [];
for (var i = 0; i < 1000; i++)
a.push(x); |
|
| Pop | var arrays = [];
for (var i = 0; i < _n; i++) {
var a = [];
for (j = 0; j < 1000; j++)
a[j] = j;
arrays[i] = a;
}
%%
var a = arrays[_i];
for (var i = 0; i < 1000; i++)
a.pop(); |
Maintaining the length of an array in a separate variable can make stack operations several times faster.
1000 push() and pop() calls interleaved |
var a = [];
%%
for (var i = 0; i < 100; i++) {
a.push(17);
a.push(19);
a.push(31);
a.pop();
a.push(83);
a.push(7);
a.pop();
a.pop();
a.pop();
a.pop();
}
|
|
1000 a[len++]= and a[--len] operations interleaved |
var a = [];
var len = 0;
%%
for (var i = 0; i < 100; i++) {
a[len++] = 17;
a[len++] = 19;
a[len++] = 31;
a[--len];
a[len++] = 83;
a[len++] = 7;
a[--len];
a[--len];
a[--len];
a[--len];
}
|
| Create empty object | ({}); |
|
| Create empty object and local variable | var x = {}; |
|
| Populate 1000 numbered properties | var x;
%%
x = {};
for (var i = 0; i < 1000; i++)
x[i] = i; |
|
| Populate 1000 numbered properties with object | var x;
var y = {};
%%
x = {};
for (var i = 0; i < 1000; i++)
x[i] = y; |
Object.isPrototypeOf() is slow: 2-3 times
slower than a simple attribute test.
| Object.isPrototypeOf() when true | function writer() {}
var x = new writer();
%%
if (writer.prototype.isPrototypeOf(x)) {
// ...
} else {
// ...
}
|
|
| Attribute test when true | function writer() {}
writer.prototype.isWriter = true;
var x = new writer();
%%
if (x.isWriter) {
// ...
} else {
// ...
}
|
|
| Object.isPrototypeOf() when trivially false | function writer() {}
var x = {};
%%
if (writer.prototype.isPrototypeOf(x)) {
// ...
} else {
// ...
}
|
|
| Attribute test when trivially false | var x = {
make: 'Ford', model: 'Taurus',
year: 1996, color: '#000066'};
%%
if (x.isWriter) {
// ...
} else {
// ...
}
|
|
| Object.isPrototypeOf() when elaborately false | function writer() {}
function reader() {}
function http_reader() {}
http_reader.prototype = new reader();
http_reader.prototype.constructor = http_reader;
var x = new http_reader();
%%
if (writer.prototype.isPrototypeOf(x)) {
// ...
} else {
// ...
}
|
|
| Attribute test when elaborately false | function writer() {}
writer.prototype.isWriter = true;
function reader() {}
function http_reader() {}
http_reader.prototype = new reader();
http_reader.prototype.constructor = http_reader;
var x = new http_reader();
%%
if (x.isWriter) {
// ...
} else {
// ...
}
|
What's the fastest possible way to build and access data structures in JavaScript?
Objects and arrays are about equally fast. Using closures to store data (a trick from functional programming) is slower.
| Create 2-property object | var nil = {};
var cell;
%%
cell = {car: nil, cdr: nil}; |
|
Create 2-property object using new |
var nil = {};
function pair(a, b) {
this.car = a;
this.cdr = b;
}
var cell;
%%
cell = new pair(nil, nil); |
|
| Create 2-element array | var nil = {};
var cell;
%%
cell = [nil, nil];
|
|
| Create closure on 2 values | var nil = {};
var cell;
var a = nil;
var b = nil;
%%
cell = function (t) { return t ? a : b; };
|
|
| Access object properties | var nil = {};
var cell = {car: nil, cdr: nil};
%%
cell.cdr; |
|
| Access array elements | var nil = {};
var cell = [nil, nil];
%%
cell[1];
|
|
| Access data hidden in closure | function cons(a, b) {
return function(t) {
return t ? a : b;
}
}
var nil = {};
var cell = cons(nil, nil);
%%
cell(false);
|
|
| Assign object properties | var nil = {};
var cell = {car: nil, cdr: nil};
%%
cell.cdr = 123;
|
|
| Assign array elements | var nil = {};
var cell = [nil, nil];
%%
cell[1] = 123;
|
if and switchOn my machine, a switch statement with five cases is
twice as fast as an if statement with five branches.
if comparing small integers, hitting the first |
var x = 0;
%%
if (x == 0) {
// ...
} else if (x == 1) {
// ...
} else if (x == 2) {
// ...
} else if (x == 3) {
// ...
} else if (x == 4) {
// ...
}
|
|
switch on small integers, hitting the first |
var x = 0;
%%
switch (x) {
case 0: break;
case 1: break;
case 2: break;
case 3: break;
case 4: break;
}
|
|
if comparing small integers, hitting the last |
var x = 4;
%%
if (x == 0) {
// ...
} else if (x == 1) {
// ...
} else if (x == 2) {
// ...
} else if (x == 3) {
// ...
} else if (x == 4) {
// ...
}
|
|
switch on small integers, hitting the last |
var x = 4;
switch (x) {
case 0: break;
case 1: break;
case 2: break;
case 3: break;
case 4: break;
}
|
|
if missing everything |
var x = 4;
%%
if (x == 61) {
// ...
} else if (x == 312) {
// ...
} else if (x == 928) {
// ...
} else if (x == 1003) {
// ...
} else if (x == 5778) {
// ...
}
|
|
switch missing everything |
var x = 4;
%%
switch (x) {
case 61: break;
case 312: break;
case 928: break;
case 1003: break;
case 5778: break;
}
|
|
if comparing characters, missing everything |
var c = 'e';
%%
if (c == '\n') {
// ...
} else if (c == 'y') {
// ...
} else if (c == 'n') {
// ...
} else if (c == 'q') {
// ...
} else if (c == 'a') {
// ...
}
|
|
switch on characters, missing everything |
var c = 'e';
%%
switch (c) {
case '\n': break;
case 'y': break;
case 'n': break;
case 'q': break;
case 'a': break;
}
|
What's the fastest way to execute different code depending on the
value of a string variable? switch is the fastest.
if |
var x = false;
var s = 'five';
%%
if (s == 'one') { x=true; }
else if (s == 'two') { x=true; }
else if (s == 'three') { x=true; }
else if (s == 'four') { x=true; }
else if (s == 'five') { x=true; }
else if (s == 'six') { x=true; }
else if (s == 'seven') { x=true; }
else if (s == 'eight') { x=true; }
else if (s == 'nine') { x=true; }
else if (s == 'ten') { x=true; }
|
|
switch |
var x = false;
var s = 'five';
%%
switch (s) {
case 'one': x=true; break;
case 'two': x=true; break;
case 'three': x=true; break;
case 'four': x=true; break;
case 'five': x=true; break;
case 'six': x=true; break;
case 'seven': x=true; break;
case 'eight': x=true; break;
case 'nine': x=true; break;
case 'ten': x=true; break;
}
|
|
| lookup table of functions | var x = false;
var obj = {
one: function () { x=true; },
two: function () { x=true; },
three: function () { x=true; },
four: function () { x=true; },
five: function () { x=true; },
six: function () { x=true; },
seven: function () { x=true; },
eight: function () { x=true; },
nine: function () { x=true; },
ten: function () { x=true; }
};
var s = 'five';
%%
obj[s]();
|
There's no speed difference between ++c and
c++ in a for loop.
All these should be equally fast. In Mozilla, the for
loop is just a hair faster than while; no clue why. And
do/while is 5-10% faster still; I have no idea what
that's all about.
The do/while test triggers IE's “run
slowly” message on my machine; the others don't. Bizarre.
for up to 100 |
var c;
%%
for (c = 0; c < 100; c++) {
}
|
|
for up to 100, preincrement |
var c;
%%
for (c = 0; c < 100; ++c) {
}
|
|
while up to 100 |
var c;
%%
c = 0;
while (c < 100) {
c++;
}
|
|
do/while up to 100 |
var c;
%%
c = 0;
do {
c++;
} while (c < 100);
|
Whitespace in source code doesn't affect speed.
(Whitespace affects running code in at least one way: when an error happens, the system tries to tell you what line of code it happened on. Some scripting languages used to implement this using an internal line-number counter, which was incremented at the beginning of every line of code. The line-number counting could actually affect performance in a tight loop. I don't know of any major languages that still do this.)
| on one line | var i = 0; %% i = Number(i); i++; i++; i++; i++; i++; i++; i++; i++; i++; i++; |
|
| on multiple lines | var i = 0; %% i = Number(i); i++; i++; i++; i++; i++; i++; i++; i++; i++; i++; |
What is the fastest way to tell whether a given character
c is one of a particular known set of characters?
Interesting results. Almost all of these are dog slow in Mozilla. Except the last one.
In IE, the last option is three times faster than the rest. In Mozilla, it's ten times faster.
| Character testing: longhand | var c = '(';
var hits = 0;
%%
if (c.length == 1 && (
(c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
|| c == '-')) {}
|
|
| Character testing: by character code | var c = '(';
var hits = 0;
%%
var i = c.charCodeAt(0);
if ((i >= 97 && i <= 122)
|| (i >= 65 && i <= 90)
|| (i >= 48 && i <= 57)
|| i == 45) {}
|
|
Character testing: with String.indexOf() |
var w = ("abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-");
var c = '(';
%%
if (w.indexOf(c) != -1) {}
|
|
| Character testing: with regular expression | var wx = /[a-zA-Z0-9-]/;
var c = '(';
var hits = 0;
%%
if (c.match(wx) !== null) {}
|
|
| Character testing: by hashtable | var c = '(';
var w = ("abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-");
var whash = {};
for (var i = 0; i < w.length; i++)
whash[w.charAt(i)] = true;
var hits = 0;
%%
if (whash[c]) {}
|
For more complex parsing, regular expressions are 20-50 times faster than a hand-coded parse routine.
| Parsing: with sloppy regular expression | var text = document.documentElement.innerHTML;
var regex = /<([\w-]+)/g;
var matchArray;
var match;
var count = 0;
%%
while ((matchArray = regex.exec(text)) !== null) {
match = matchArray[1];
count++;
}
|
|
| Parsing: with sloppy hand-coded loop | var text = document.documentElement.innerHTML;
var i;
var match;
var count = 0;
var tagNameChars = (
'abcdefghijklmnopqrstuvwxyz'
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-');
%%
i = 0;
while (i < text.length) {
// search for angle bracket
while (i < text.length && text.charAt(i) != '<')
i++;
if (++i >= text.length)
break;
var c = text.charAt(i);
if (tagNameChars.indexOf(c) == -1)
continue;
var b = i;
while (++i < text.length
&& tagNameChars.indexOf(text.charAt(i)) != -1)
;
match = text.substring(b, i);
count++;
}
|
Overhead. All results include the overhead of a
simple for loop; run the “Empty loop” test to
see how much this is.
Accuracy. Forget it. The results are very noisy. Double-click any cell two or three times and you'll get different results. I imagine you can get maybe one significant digit out of these, maybe a bit less.
How it works. For each test, the tester uses
new Function() to create a function that takes a positive
whole number N and times the execution of the test code
N times in a tight loop. Then it calls the function
repeatedly, with N=1, then 2, then 5, 10, 20, 50, and so on
until the loop actually takes a significant amount of time to execute
(at least 200ms). It does this 5 times, throws out the worst time,
and averages the other four. This average is what's displayed.
The tester code is written in continuation-passing style, an extremely weird way to program that helps to avoid triggering the infamous “A script on this page is making Internet Explorer run slowly” popup. Alas, this message pops up on the fastest tests regardless, unless you use the workaround described at the top of the page.