Last active
January 9, 2020 23:28
-
-
Save dherman/3d0b4733303eaf4bae5e to your computer and use it in GitHub Desktop.
calculating the geometric mean in normal JS and asm.js
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
// Calculating the geometric mean in log space avoids overflow when | |
// working with anything more than very small data sets. | |
// | |
// The formula can be calculated with simple algebraic identities: | |
// | |
// n√(Prod^n x_i) | |
// = { e^ln(x) = x } | |
// n√(Prod^n e^ln(x_i)) | |
// = { e^a x e^b = e^(a+b) } | |
// n√(e^(Sum^n ln(x_i))) | |
// = { z√(x^y) = x^(y / z) } | |
// e((Sum^n ln(x_i)) / n) | |
function PlainModule(buffer) { | |
var values = new Float64Array(buffer); | |
function logSum(start, end) { | |
var sum = 0; | |
for (var i = start; i < end; i++) { | |
sum += Math.log(values[i]); | |
} | |
return sum; | |
} | |
function geometricMean(start, end) { | |
return Math.exp(logSum(start, end) / (end - start)); | |
} | |
return { geometricMean: geometricMean }; | |
} | |
function ASMModule(stdlib, foreign, buffer) { | |
"use asm"; | |
var exp = stdlib.Math.exp; | |
var log = stdlib.Math.log; | |
var values = new stdlib.Float64Array(buffer); | |
function logSum(start, end) { | |
start = start|0; | |
end = end|0; | |
var sum = 0.0, p = 0, q = 0; | |
// asm.js forces byte addressing of the heap by requiring shifting by 3 | |
for (p = start << 3, q = end << 3; (p|0) < (q|0); p = (p + 8)|0) { | |
sum = sum + +log(values[p>>3]); | |
} | |
return +sum; | |
} | |
function geometricMean(start, end) { | |
start = start|0; | |
end = end|0; | |
return +exp(+logSum(start, end) / +((end - start)|0)); | |
} | |
return { geometricMean: geometricMean }; | |
} | |
var fakeWindow = { | |
Float64Array: Float64Array, | |
Math: { | |
log: (x) => Math.log(x), | |
exp: (x) => Math.exp(x) | |
} | |
}; | |
function time(thunk) { | |
var start = performance.now(); | |
var result = thunk(); | |
var end = performance.now(); | |
return { | |
result: result, | |
time: end - start | |
}; | |
} | |
function build(length) { | |
var result = new Float64Array(length); | |
for (var i = 0; i < length; i++) { | |
result[i] = i + 1; | |
} | |
return result; | |
} | |
var values = build(0x10000); | |
var fast = ASMModule(window, null, values.buffer); | |
var slow = ASMModule(fakeWindow, null, values.buffer); | |
var plain = PlainModule(values.buffer); | |
console.log(time(() => fast.geometricMean(0, 0x10000))); | |
console.log(time(() => slow.geometricMean(0, 0x10000))); | |
console.log(time(() => plain.geometricMean(0, 0x10000))); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment