Skip to content

Instantly share code, notes, and snippets.

@vastus
Last active April 6, 2023 21:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save vastus/8018bdd2448a17e17d4d98adfa26786c to your computer and use it in GitHub Desktop.
Save vastus/8018bdd2448a17e17d4d98adfa26786c to your computer and use it in GitHub Desktop.
Imperative and declarative
/*
* 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))
});
@vastus
Copy link
Author

vastus commented Apr 27, 2019

[impdec] % node --version
v8.10.0
[impdec] % node impdec.js
imperative: 3.211ms
declarative: 130.234ms
mydeclarative: 23.874ms
twoPasses: 28.304ms
singlePassNative: 18.319ms
singlePassOwn: 6.626ms
singlePassOwnInlinedReduce: 3.636ms
imperative: 1.689ms
declarative: 131.305ms
mydeclarative: 46.471ms
twoPasses: 34.786ms
singlePassNative: 13.581ms
singlePassOwn: 8.302ms
singlePassOwnInlinedReduce: 1.780ms
imperative: 1.274ms
declarative: 126.704ms
mydeclarative: 43.090ms
twoPasses: 37.398ms
singlePassNative: 13.259ms
singlePassOwn: 8.475ms
singlePassOwnInlinedReduce: 1.176ms
imperative: 1.186ms
declarative: 134.573ms
mydeclarative: 44.923ms
twoPasses: 33.224ms
singlePassNative: 13.491ms
singlePassOwn: 8.581ms
singlePassOwnInlinedReduce: 1.199ms

@jruusu
Copy link

jruusu commented Aug 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