Skip to content

Instantly share code, notes, and snippets.

@stevebest
Created June 30, 2012 18:34
Show Gist options
  • Select an option

  • Save stevebest/3024992 to your computer and use it in GitHub Desktop.

Select an option

Save stevebest/3024992 to your computer and use it in GitHub Desktop.
Yet another pseudo-scientific V8 performance test
<!doctype html>
<html>
<head>
<title>Yet another JS performance test</title>
</head>
<body>
<h1>Look at the console...</h1>
<table>
<thead>
<tr>
<th>Test name</th>
<th>min, ms</th>
<th>max, ms</th>
<th>avg, ms</th>
</tr>
</thead>
<tbody id="stat"></tbody>
</table>
<script type="text/javascript" src="points.js"></script>
<script type="text/javascript">
var table = document.getElementById('stat');
testAll(function (name, stat) {
var tr = document.createElement('tr');
tr.innerHTML = '<th>' + name + '</th>' +
'<td>' + stat.min + '</td>' +
'<td>' + stat.max + '</td>' +
'<td>' + stat.avg + '</td>';
table.appendChild(tr);
console.log(name, stat);
});
</script>
</body>
</html>
// cc points.c -O3 -lm -o points
#define N 100000
#define M 100
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
double xs[N];
double ys[N];
double calc() {
int i;
double sum = 0.0;
for (i = 0; i < N; i++) {
sum += sqrt(xs[i] * xs[i] + ys[i] * ys[i]);
}
return sum;
}
int main() {
int i;
for (i = 0; i < N; i++) {
xs[i] = rand();
ys[i] = rand();
}
clock_t start, end;
start = clock();
for (i = 0; i < M; i++) {
calc();
}
end = clock();
double diff = ((double) (end - start)) / CLOCKS_PER_SEC;
printf("%.5lf seconds\n", diff);
return 0;
}
//
// Abstract
// --------
//
// Here's the deal. Everyone knows V8 is awesome because of hidden classes it
// creates for our JS objects. If we cooperate with V8, performance rocks. If
// we put spanners in the works, performance suffers.
//
// In this test, we create N = 100,000 points inside a unit square and
// calculate a total distance from each of them to (0,0) using different ways
// to access `x` and `y` properties of the point:
//
// - by reading them directly in function `distance`;
// - from a method `distanceInstance` created inside constructor function
// using `this.distanceInstance = function ...` approach;
// - from a closure function `distanceClosure` created inside constructor
// function and accessing constructor parameters instead of object fields;
// - from a method `distancePrototype` defined on a prototype object
// using `PointClass.prototype.distancePrototype = function ....` approach.
//
// In addition to the expected `Point` class we create `BadPoint` class which
// tries to mess with the structure of internal classes of V8 and makes
// accessing properties inside of a tight loop as hard as possible.
//
// We then run a measurement M = 100 times for each of the methods and get the
// min, max and avg times for them, using arrays of 'good' and 'bad' points
// separately.
//
// For maximum performance, we run `vectorized` test, which doesn't use arrays
// of objects, avoiding the costs of properties access and function calls.
// Instead, it uses two parallel numerical arrays to represent points.
//
//
// Results
// -------
//
// Intel(R) Core(TM)2 CPU T5200 @ 1.60GHz.
// Ubuntu Linux 3.2.0-26-generic-pae.
// All V8 builds are 32-bit.
//
// Numbers are in milliseconds.
//
// ### node v0.6.19, v8 3.6.6.25
//
// min max avg
// Point, distance(p) 10 13 10.93
// Point, p.distanceInstance() 17 21 18.93
// Point, p.distanceClosure() 15 18 15.84
// Point, p.distancePrototype() 7 9 7.77
//
// BadPoint, distance(p) 16 29 17.17
// BadPoint, p.distanceInstance() 22 24 22.51
// BadPoint, p.distanceClosure() 17 21 18.03
// BadPoint, p.distancePrototype() 20 22 20.71
//
// vectorized 3 6 3.74
// ### node 0.8.1, v8 3.11.10.12
//
// min max avg
// Point, distance(p) 3 12 4.05
// Point, p.distanceInstance() 19 43 20.90
// Point, p.distanceClosure() 16 20 17.21
// Point, p.distancePrototype() 6 8 7.02
//
// BadPoint, distance(p) 15 88 16.39
// BadPoint, p.distanceInstance() 24 114 26.43
// BadPoint, p.distanceClosure() 18 26 20.44
// BadPoint, p.distancePrototype() 19 46 20.66
//
// vectorized 3 5 3.75
// ### Chrome 20.0.1132.47, v8 3.10.8.19 (or is it?)
//
// min max avg
// Point, distance(p) 4 10 4.81
// Point, p.distanceInstance() 21 40 22.67
// Point, p.distanceClosure() 18 23 19.18
// Point, p.distancePrototype() 7 10 7.78
//
// BadPoint, distance(p) 15 23 18.04
// BadPoint, p.distanceInstance() 27 134 29.54
// BadPoint, p.distanceClosure() 21 27 21.92
// BadPoint, p.distancePrototype() 19 20 19.45
//
// vectorized 3 6 3.75
// ### C implementation of `vectorized`
//
// $ cc points.c -O3 -lm -o points
// $ ./points
// 0.43000 seconds
//
// And, just for fun, the C implementation of `vectorized` takes 0.43 seconds
// to complete M = 100 iterations, which makes it _slower_ than it's JavaScript
// counterpart!
//
// Conclusions
// -----------
//
// As predicted, working with `BadPoint` class was slower than with `Point`
// due to its unpredictable inner structure. Using closures to read
// constructor parameters instead of looking up object fields has helped only
// marginally. Using the `distancePrototype` method was significantly faster
// than other approaches of accessing properties of `this`.
//
// When testing in Chrome, one unusual effect was noticed. When the test was
// run with the devtools console open, `distanceInstance` and
// `distanceClosure` were performing terribly - that is, about 5x slower than
// with the console closed. Moreover, refreshing the test page to re-run the
// test has led to all the tests performing equally bad.
//
var N = 100000; // number of points
var M = 100; // number of iterations
// A 'good' point constructor, which should play nicely with V8.
function Point(x, y) {
this.x = x;
this.y = y;
this.distanceInstance = function () {
return Math.sqrt(this.x * this.x + this.y * this.y);
};
this.distanceClosure = function () {
return Math.sqrt(x * x + y * y);
}
}
Point.prototype.distancePrototype = function () {
return Math.sqrt(this.x * this.x + this.y * this.y);
};
// A 'bad' point constructor, which has the properties of a `Point`, but
// messes up their order and thus comes up with the different hidden class
// each time.
function BadPoint(x, y) {
if (x > y) {
this.y = y;
}
if (Math.random() > 0.5) this.foo = 'foo';
if (x <= y) {
this.x = x;
}
if (Math.random() > 0.5) this.bar = 'bar';
if (x <= y) {
this.y = y;
}
if (Math.random() > 0.5) this.baz = 'baz';
if (x > y) {
this.x = x;
}
this.distanceInstance = function() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
this.distanceClosure = function () {
return Math.sqrt(x * x + y * y);
}
}
BadPoint.prototype.distancePrototype = function () {
return Math.sqrt(this.x * this.x + this.y * this.y);
};
function distance(p) {
return Math.sqrt(p.x * p.x + p.y * p.y);
}
function runFunction(points) {
var sum = 0.0;
for (var i = 0; i < points.length; i++) {
sum += distance(points[i]);
}
return sum;
}
function runInstance(points) {
var sum = 0.0;
for (var i = 0; i < points.length; i++) {
sum += points[i].distanceInstance();
}
return sum;
}
function runClosure(points) {
var sum = 0.0;
for (var i = 0; i < points.length; i++) {
sum += points[i].distanceClosure();
}
return sum;
}
function runPrototype(points) {
var sum = 0.0;
for (var i = 0; i < points.length; i++) {
sum += points[i].distancePrototype();
}
return sum;
}
function testAll(report) {
function test(points, run, name) {
var stat = {
min: Infinity,
max: -Infinity
};
var totalStart = Date.now();
for (var iter = 0; iter < M; iter++) {
var start = Date.now();
var sum = run(points);
var duration = Date.now() - start;
//if ((iter + 1) % 100 === 0) console.log(sum);
stat.min = Math.min(duration, stat.min);
stat.max = Math.max(duration, stat.max);
}
var totalDuration = Date.now() - totalStart;
stat.avg = totalDuration / M;
report.call(null, name, stat);
}
var points = new Array(N);
var i = 0;
// init 'good' points
for (i = 0; i < N; i++) {
points[i] = new Point(Math.random(), Math.random());
}
test(points, runFunction, 'Point, distance(p) ');
test(points, runInstance, 'Point, p.distanceInstance() ');
test(points, runClosure, 'Point, p.distanceClosure() ');
test(points, runPrototype, 'Point, p.distancePrototype()');
// init 'bad' points
for (i = 0; i < N; i++) {
points[i] = new BadPoint(Math.random(), Math.random());
}
test(points, runFunction, 'BadPoint, distance(p) ');
test(points, runInstance, 'BadPoint, p.distanceInstance() ');
test(points, runClosure, 'BadPoint, p.distanceClosure() ');
test(points, runPrototype, 'BadPoint, p.distancePrototype()');
// init 'vectorized' points
var xs = [];
var ys = [];
for (i = 0; i < N; i++) {
xs[i] = Math.random();
ys[i] = Math.random();
}
function runVectorized() {
var sum = 0.0;
for (var i = 0; i < N; i++) {
var x = xs[i], y = ys[i];
sum += Math.sqrt(x * x + y * y);
}
return sum;
}
test(null, runVectorized, 'vectorized');
}
if (typeof window === 'undefined') {
testAll(function (name, stat) {
console.log(name, stat);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment