Skip to content

Instantly share code, notes, and snippets.

@VictorTaelin
Last active August 15, 2016 21:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save VictorTaelin/6d4b15a496df7c281a0dffef6923d433 to your computer and use it in GitHub Desktop.
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 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