Created
June 30, 2012 18:34
-
-
Save stevebest/3024992 to your computer and use it in GitHub Desktop.
Yet another pseudo-scientific V8 performance test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // 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