Last active
August 15, 2016 21:52
-
-
Save VictorTaelin/6d4b15a496df7c281a0dffef6923d433 to your computer and use it in GitHub Desktop.
As of 2016, manually inlining JS funcalls seemingly still beats engines by a large margin
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
// This is supporting evidence that manually inlining functions still beats | |
// most JS engines. There are three equivalent functions, `a`, `b` and `c`, | |
// with varying levels of manual inlining. On my tests, (node v5.9.1, 2016 | |
// macbook 12"), `c` is approx. 17 times faster than `a`, and 3.5 times faster | |
// than `b`. (Edit: the difference increased further by avoiding .apply) | |
function V3(x, y, z){ return {x: x, y: y, z: z}; }; | |
function Qt(x, y, z, w){ return {x: x, y: y, z: y, w: w}; }; | |
function Cam(pos, rot){ return {pos: pos, rot: rot}; }; | |
function rotate(q, v){ | |
// ported from three.js | |
var ix = q.w * v.x + q.y * v.z - q.z * v.y; | |
var iy = q.w * v.y + q.z * v.x - q.x * v.z; | |
var iz = q.w * v.z + q.x * v.y - q.y * v.x; | |
var iw = -q.x * v.x - q.y * v.y - q.z * v.z; | |
return V3( | |
ix * q.w + iw * - q.x + iy * - q.z - iz * - q.y, | |
iy * q.w + iw * - q.y + iz * - q.x - ix * - q.z, | |
iz * q.w + iw * - q.z + ix * - q.y - iy * - q.x); | |
}; | |
// non inlined implementation | |
var a = (function(){ | |
function zipWith(f, a, b){ | |
return {x: f(a.x, b.x), y: f(a.y, b.y), z: f(a.z, b.z)}; | |
}; | |
function map(f, a){ | |
return {x: f(a.x), y: f(a.y), z: f(a.z)}; | |
}; | |
function foldr(c, n, a){ | |
return c(a.x, c(a.y, c(a.z, n))); | |
}; | |
function dot(a, b){ | |
return foldr(((a,b)=>a+b), 0, zipWith((a,b)=>a*b, a, b)); | |
}; | |
function scale(s, v){ | |
return map((x)=>x*s, v); | |
}; | |
function sub(a, b){ | |
return zipWith((a,b)=>a-b, a, b); | |
}; | |
return function(pos, cam){ | |
var vec = sub(pos, cam.pos); | |
var camAxis = V3( | |
rotate(cam.rot, V3(1, 0, 0)), | |
rotate(cam.rot, V3(0, 1, 0)), | |
rotate(cam.rot, V3(0, 0, 1))); | |
var normDist = dot(vec, camAxis.y); | |
var normProj = scale(normDist, camAxis.y); | |
var posOnPlane = sub(vec, normProj); | |
var x = dot(posOnPlane, camAxis.x); | |
var y = dot(posOnPlane, camAxis.z); | |
return V3(x, y, normDist); | |
}; | |
})(); | |
// inlined high order functions | |
var b = (function(){ | |
function dot(a, b){ | |
return a.x*b.x + a.y*b.y + a.z*b.z; | |
}; | |
function scale(s, v){ | |
return V3(v.x*s, v.y*s, v.z*s); | |
}; | |
function sub(a, b){ | |
return V3(a.x-b.x, a.y-b.y, a.z-b.z); | |
}; | |
return function(pos, cam){ | |
var vec = sub(pos, cam.pos); | |
var camAxis = V3( | |
rotate(cam.rot, V3(1, 0, 0)), | |
rotate(cam.rot, V3(0, 1, 0)), | |
rotate(cam.rot, V3(0, 0, 1))); | |
var normDist = dot(vec, camAxis.y); | |
var normProj = scale(normDist, camAxis.y); | |
var posOnPlane = sub(vec, normProj); | |
var x = dot(posOnPlane, camAxis.x); | |
var y = dot(posOnPlane, camAxis.z); | |
return V3(x, y, normDist); | |
}; | |
})(); | |
// inlined everything | |
var c = function(pos, cam){ | |
var cp = cam.pos; | |
var vx = pos.x - cp.x; | |
var vy = pos.y - cp.y; | |
var vz = pos.z - cp.z; | |
var cr = cam.rot; | |
var crx = cr.x; | |
var cry = cr.y; | |
var crz = cr.z; | |
var crw = cr.w; | |
var caxx = crw * crw - crx * -crx + crz * - crz + cry * - cry; | |
var caxy = crz * crw - crx * -cry - cry * - crx - crw * - crz; | |
var caxz = -cry * crw - crx * -crz + crw * - cry - crz * - crx; | |
var cayx = -crz * crw - cry * -crx + crw * - crz - crx * - cry; | |
var cayy = crw * crw - cry * -cry + crx * - crx + crz * - crz; | |
var cayz = crx * crw - cry * -crz - crz * - cry - crw * - crx; | |
var cazx = cry * crw - crz * -crx - crx * - crz - crw * - cry; | |
var cazy = -crx * crw - crz * -cry + crw * - crx - cry * - crz; | |
var cazz = crw * crw - crz * -crz + cry * - cry + crx * - crx; | |
var nd = vx * cayx + vy * cayy + vz * cayz; | |
var nx = nd * cayx; | |
var ny = nd * cayy; | |
var nz = nd * cayz; | |
var ppx = vx - nx; | |
var ppy = vy - ny; | |
var ppz = vz - nz; | |
var x = ppx * caxx + ppy * caxy + ppz * caxz; | |
var y = ppx * cazx + ppy * cazy + ppz * cazz; | |
return V3(x, y, nd); | |
}; | |
function randomTest(){ | |
var r = Math.random; | |
return [ | |
V3(r(), r(), r()), | |
Cam(V3(r(), r(), r()), Qt(r(), r(), r(), r()))]; | |
}; | |
function callsPerSec(fn){ | |
for (var i=0, t=Date.now(); Date.now()-t < 1000; ++i) | |
fn(); | |
return i; | |
}; | |
console.log("Test if the functions are actually equivalent:"); | |
var test = randomTest(); | |
[a, b, c].map(function(fn, i){ | |
console.log("version "+i+": "+JSON.stringify(fn.apply(null, test))); | |
}); | |
console.log("Calls per second:"); | |
[a, b, c].map(function(fn, i){ | |
var test = randomTest(); | |
console.log("version "+i+": "+callsPerSec(function(){ | |
for (var i=0; i<100; ++i) | |
fn(test[0], test[1]); | |
})); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment