Last active
December 20, 2015 00:59
-
-
Save gmalysa/6045767 to your computer and use it in GitHub Desktop.
Test of some simple function optimizations for javascript/nodejs.
This file contains 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
// Test 1: Do simple functions get inlined? | |
var iter = 10000; | |
var start, stop; | |
function inc(a) { return a+1; } | |
// Wrap test in a function to allow warmup of optimizer | |
function test1_fn(iter) { | |
var x; | |
for (var i = 0; i < iter; ++i) { | |
x = inc(x); | |
} | |
} | |
// Warmup, then run real test | |
test1_fn(); | |
start = process.hrtime(); | |
test1_fn(); | |
stop = process.hrtime(start); | |
time = (stop[0]*1e9 + stop[1])/1e6; | |
console.log('T1//with function: '+time+' ms'); | |
function test1_inline(iter) { | |
var x; | |
for (var i = 0; i < iter; ++i) { | |
x = x + 1; | |
} | |
} | |
test1_inline(iter); | |
start = process.hrtime(); | |
test1_inline(iter); | |
stop = process.hrtime(start); | |
time = (stop[0]*1e9 + stop[1])/1e6; | |
console.log('T1//hand inlined: '+time+' ms'); | |
console.log(''); | |
// On my machine, ~ 0.4670 ms vs. 0.0365 ms | |
// Therefore: the answer is no. | |
// Test 2: Iteration methods vs. for loops | |
iter = 1000; | |
var elems = 100, subelems = 10, sum; | |
var xa = []; | |
for (i = 0; i < elems; ++i) { | |
xa[i] = Math.random(); | |
} | |
// 2a: array sum with reduce | |
function test2a_reduce() { | |
var sum; | |
sum = xa.reduce(function(memo, v) { | |
return memo + v; | |
}, 0); | |
return sum; | |
} | |
sum = test2a_reduce(); | |
start = process.hrtime(); | |
sum = test2a_reduce(); | |
stop = process.hrtime(start); | |
time = (stop[0]*1e9 + stop[1])/1e6; | |
console.log('T2A//using reduce: '+time+' ms'); | |
function test2a_loop() { | |
var sum = 0; | |
for (var i = 0; i < xa.length; ++i) { | |
sum += xa[i]; | |
} | |
return sum; | |
} | |
sum = test2a_loop(); | |
start = process.hrtime(); | |
sum = test2a_loop(); | |
stop = process.hrtime(start); | |
time = (stop[0]*1e9 + stop[1])/1e6; | |
console.log('T2A//for loop: '+time+' ms'); | |
console.log(''); | |
// On my machine, times fluctuate a lot, but 0.10-0.16 ms vs 0.0054-0.0078 ms | |
// so the for loop is faster again | |
// 2b: nested iteration, calling a member function for each element of an array | |
function t2b_class() { | |
this.val = Math.random(); | |
} | |
var t2b_sum = 0; | |
t2b_class.prototype.work = function() { | |
t2b_sum += this.val; | |
}; | |
for (i = 0; i < elems; ++i) { | |
xa[i] = []; | |
for (j = 0; j < elems; ++j) { | |
xa[i][j] = new t2b_class(); | |
} | |
} | |
function test2b_nested(xa) { | |
xa.forEach(function(v) { | |
v.forEach(function(v2) { | |
v2.work(); | |
}); | |
}); | |
} | |
test2b_nested(xa); | |
start = process.hrtime(); | |
test2b_nested(xa); | |
stop = process.hrtime(start); | |
time = (stop[0]*1e9 + stop[1])/1e6; | |
console.log('T2B//nested iterators: '+time+' ms'); | |
function inner_iter(v2) { | |
v2.work(); | |
} | |
function test2b_inner(xa) { | |
xa.forEach(function(v) { | |
v.forEach(inner_iter); | |
}); | |
} | |
test2b_inner(xa); | |
start = process.hrtime(); | |
test2b_inner(xa); | |
stop = process.hrtime(start); | |
time = (stop[0]*1e9 + stop[1])/1e6; | |
console.log('T2B//nested iterators, avoids redeclaration: '+time+' ms'); | |
function test2b_loop(xa) { | |
for (var i = 0; i < xa.length; ++i) { | |
for (var j = 0; j < xa[i].length; ++j) { | |
xa[i][j].work(); | |
} | |
} | |
} | |
test2b_loop(xa); | |
start = process.hrtime(); | |
test2b_loop(xa); | |
stop = process.hrtime(start); | |
time = (stop[0]*1e9 + stop[1])/1e6; | |
console.log('T2B//for loop: '+time+' ms'); | |
console.log(''); | |
// Again, some fluctuation, but roughly 1.08-1.32 ms vs 0.26-0.35 ms on my machine | |
// Test 3: Tail calls vs. stack walking vs. iterative method | |
function fact_tail(n, accumulator) { | |
if (n < 2) | |
return accumulator; | |
return fact_tail(n-1, accumulator*n); | |
} | |
function fact_stack(n) { | |
if (n < 2) | |
return 1; | |
return n * fact_stack(n-1); | |
} | |
function fact_iter(n) { | |
var accumulator = n; | |
for (n = n-1; n > 1; --n) { | |
accumulator = accumulator*n; | |
} | |
return accumulator; | |
} | |
iter = 1000; | |
var n = 30, result; | |
function test3_tail(iter, n) { | |
var result; | |
for (var i = 0; i < iter; ++i) { | |
result = fact_tail(n-1, n); | |
} | |
} | |
test3_tail(iter, n); | |
start = process.hrtime(); | |
test3_tail(iter, n); | |
stop = process.hrtime(start); | |
time = (stop[0]*1e9 + stop[1])/1e6; | |
console.log('T3//Tail Factorial: '+time+' ms'); | |
function test3_stack(iter, n) { | |
var result; | |
for (var i = 0; i < iter; ++i) { | |
result = fact_stack(n); | |
} | |
} | |
test3_stack(iter, n); | |
start = process.hrtime(); | |
test3_stack(iter, n); | |
stop = process.hrtime(start); | |
time = (stop[0]*1e9 + stop[1])/1e6; | |
console.log('T3//Stack Factorial: '+time+' ms'); | |
function test3_iter(iter, n) { | |
var result; | |
for (var i = 0; i < iter; ++i) { | |
result = fact_iter(n); | |
} | |
} | |
test3_iter(iter, n); | |
start = process.hrtime(); | |
test3_iter(iter, n); | |
stop = process.hrtime(start); | |
time = (stop[0]*1e9 + stop[1])/1e6; | |
console.log('T3//Iterative Factorial: '+time+' ms'); | |
console.log(''); | |
// Times: 0.277 ms (tail) vs. 0.515 (stack) vs. 0.181 (iterative) | |
// Tail and iterative should be the same time if tail calls are fully optimized | |
// Test 4: Function table overhead | |
function TestClass(a, b, c) { | |
this.a = a; | |
this.b = b; | |
this.c = c; | |
} | |
TestClass.prototype.hypot = function() { | |
return Math.sqrt(this.a*this.a + this.b*this.b + this.c*this.c); | |
} | |
iter = 10000; | |
var fn = TestClass.prototype.hypot; | |
xa = new TestClass(Math.random(), Math.random(), Math.random()); | |
function test4_object(iter, xa) { | |
var result; | |
for (var i = 0; i < iter; ++i) { | |
result = xa.hypot(); | |
} | |
} | |
test4_object(iter, xa); | |
start = process.hrtime(); | |
test4_object(iter, xa); | |
stop = process.hrtime(start); | |
time = (stop[0]*1e9 + stop[1])/1e6; | |
console.log('T4//Object method lookup: '+time+' ms'); | |
function test4_prototype(iter, xa) { | |
var result; | |
for (var i = 0; i < iter; ++i) { | |
result = TestClass.prototype.hypot.call(xa); | |
} | |
} | |
test4_prototype(iter, xa); | |
start = process.hrtime(); | |
test4_prototype(iter, xa); | |
stop = process.hrtime(start); | |
time = (stop[0]*1e9 + stop[1])/1e6; | |
console.log('T4//Prototype method lookup: '+time+' ms'); | |
function test4_pointer(iter, xa) { | |
var result; | |
for (var i = 0; i < iter; ++i) { | |
result = fn.call(xa); | |
} | |
} | |
test4_pointer(iter, xa); | |
start = process.hrtime(); | |
test4_pointer(iter, xa); | |
stop = process.hrtime(start); | |
time = (stop[0]*1e9 + stop[1])/1e6; | |
console.log('T4//Pre-saved method pointer: '+time+' ms'); | |
console.log(''); | |
// 0.194 ms (lookup) vs 0.172 ms (prototype) vs 0.135 ms (presaved) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment