Last active
April 6, 2023 21:57
-
-
Save vastus/8018bdd2448a17e17d4d98adfa26786c to your computer and use it in GitHub Desktop.
Imperative and declarative
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
/* | |
* Notes | |
* | |
* Make sure you know the size of your data and use proper functions or style | |
* to not introduce bottlenecks by mistake. | |
* | |
* If we get inlined functions to Node/JS, using declarative programs will be | |
* almost as fast as imperative ones by the magic of transducers. Transducers | |
* will do a single pass (reduce) on the input with the composition of the | |
* given transformation functions. | |
* | |
* As this simple example benchmark shows that the `imperative` and | |
* `singlePassInlinedReduce` are performing almost at the same rate. | |
* | |
* Also it's worth to mention that the single pass reduce version isn't too | |
* bad either; roughly ~6 times slower than the imperative one. | |
* | |
* Motivated by: https://jackmott.github.io/programming/2016/07/22/making-obvious-fast.html | |
* Thanks Otto :) | |
*/ | |
/* | |
* Helpers | |
*/ | |
function withTime(f, ...args) { | |
console.time(f.name); | |
const x = f(...args); | |
console.timeEnd(f.name); | |
return x; | |
} | |
// Note: eager. | |
function range(from, until) { | |
return Array | |
.from(Array(until - from).keys()) | |
.map(x => x + from); | |
} | |
function map(f, xs) { | |
const ys = []; | |
for (let i = 0; i < xs.length; i++) { | |
ys.push(f(xs[i])); | |
} | |
return ys; | |
} | |
function reduce(f, initial, xs) { | |
let acc = initial; | |
for (let i = 0; i < xs.length; i++) { | |
acc = f(acc, xs[i]); | |
} | |
return acc; | |
} | |
/* | |
* Implementations | |
*/ | |
function imperative(values) { | |
var sum = 0.0; | |
for (var i = 0; i < values.length;i++){ | |
var x = values[i]; | |
sum += x*x; | |
} | |
return sum; | |
} | |
function declarative(values) { | |
return values | |
.map(x => x * x) | |
.reduce((total, num) => total + num, 0); | |
} | |
function mydeclarative(values) { | |
return reduce((total, num) => total + num, 0, map(x => x * x, values)) | |
} | |
function twoPasses(values) { | |
const ys = []; | |
for (let i = 0; i < xs.length; i++) { | |
ys.push(xs[i] * xs[i]); | |
} | |
let acc = 0; | |
for (let i = 0; i < xs.length; i++) { | |
acc = acc + xs[i]; | |
} | |
return acc; | |
} | |
function singlePassNative(values) { | |
return values.reduce((acc, elem) => acc + elem * elem, 0); | |
} | |
function singlePassOwn(values) { | |
return reduce((acc, elem) => acc + elem * elem, 0, values); | |
} | |
function singlePassOwnInlinedReduce(values) { | |
const f = (acc, elem) => acc + elem * elem | |
let acc = 0; | |
for (let i = 0; i < values.length; i++) { | |
acc = f(acc, values[i]); | |
} | |
return acc; | |
} | |
/* | |
* Run 'em. | |
*/ | |
const xs = range(0, 1000000); | |
// call the functions a few times | |
// to make sure JIT kicks in | |
range(0, 4) | |
.forEach(() => { | |
[ | |
imperative, | |
declarative, | |
mydeclarative, | |
twoPasses, | |
singlePassNative, | |
singlePassOwn, | |
singlePassOwnInlinedReduce, | |
].map(f => withTime(f, xs)) | |
}); |
Author
vastus
commented
Apr 27, 2019
•
Node 8.10 vs 10.x
$ nvm use 8.10 && node -v && node impdec.js
Now using node v8.10.0 (npm v5.6.0)
v8.10.0
imperative: 3.424ms
declarative: 142.586ms
mydeclarative: 18.380ms
twoPasses: 24.404ms
singlePassNative: 16.825ms
singlePassOwn: 6.577ms
singlePassOwnInlinedReduce: 3.696ms
imperative: 1.890ms
declarative: 143.729ms
mydeclarative: 43.538ms
twoPasses: 30.617ms
singlePassNative: 13.774ms
singlePassOwn: 9.220ms
singlePassOwnInlinedReduce: 2.129ms
imperative: 1.415ms
declarative: 137.369ms
mydeclarative: 42.239ms
twoPasses: 30.252ms
singlePassNative: 13.778ms
singlePassOwn: 9.477ms
singlePassOwnInlinedReduce: 1.346ms
imperative: 1.365ms
declarative: 142.440ms
mydeclarative: 42.942ms
twoPasses: 27.667ms
singlePassNative: 13.935ms
singlePassOwn: 9.019ms
singlePassOwnInlinedReduce: 1.358ms
$ nvm use 10 && node -v && node impdec.js
Now using node v10.15.3 (npm v6.4.1)
v10.15.3
imperative: 4.197ms
declarative: 135.422ms
mydeclarative: 21.944ms
twoPasses: 20.396ms
singlePassNative: 16.946ms
singlePassOwn: 12.302ms
singlePassOwnInlinedReduce: 3.804ms
imperative: 1.760ms
declarative: 135.858ms
mydeclarative: 38.488ms
twoPasses: 32.304ms
singlePassNative: 14.540ms
singlePassOwn: 9.292ms
singlePassOwnInlinedReduce: 1.884ms
imperative: 1.453ms
declarative: 138.013ms
mydeclarative: 39.823ms
twoPasses: 29.577ms
singlePassNative: 14.111ms
singlePassOwn: 9.243ms
singlePassOwnInlinedReduce: 1.285ms
imperative: 1.398ms
declarative: 138.234ms
mydeclarative: 39.109ms
twoPasses: 28.115ms
singlePassNative: 14.748ms
singlePassOwn: 9.133ms
singlePassOwnInlinedReduce: 1.267ms
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment