Skip to content

Instantly share code, notes, and snippets.

@jesseduffield
Created July 11, 2020 09:05
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 jesseduffield/180475f97f50d7351903b81aed5b1c2e to your computer and use it in GitHub Desktop.
Save jesseduffield/180475f97f50d7351903b81aed5b1c2e to your computer and use it in GitHub Desktop.
Benchmarking array functions
var Benchmark = require('benchmark');
var suite = new Benchmark.Suite();
const testMap = (array, transform) => {
return array.map(item => transform(item));
};
const testReduce = (array, transform) => {
return array.reduce((acc, item) => acc.concat(transform(item)), []);
};
const testForEach = (array, transform) => {
let result = [];
array.forEach(item => {
result.push(transform(item));
});
return result;
};
const testForLoop = (array, transform) => {
let result = [];
for (i = 0; i < array.length; i++) {
result.push(transform(array[i]));
}
return result;
};
const testForEachInPlace = (array, transform) => {
array.forEach((item, i) => {
array[i] = transform(item);
});
return array;
};
const testForLoopInPlace = (array, transform) => {
for (i = 0; i < array.length; i++) {
array[i] = transform(array[i]);
}
return array;
};
const runSuite = (createArray, transform) => {
suite
.add('map', testMap.bind(null, createArray(), transform))
.add('reduce', testReduce.bind(null, createArray(), transform))
.add('forEach', testForEach.bind(null, createArray(), transform))
.add('for-loop', testForLoop.bind(null, createArray(), transform))
.add(
'forEach in-place',
testForEachInPlace.bind(null, createArray(), transform)
)
.add(
'for-loop in-place',
testForLoopInPlace.bind(null, createArray(), transform)
)
.on('cycle', event => {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({ async: false });
};
runSuite(
() => Array.from(Array(1000).map(() => ({ key: 'value' }))),
item => ({
...item,
// idempotent by design so repeated transforms makes no difference
key: 'value',
})
);
// these don't play nice right now so you might need to comment one `runSuite` call out to run the other one
runSuite(() => Array.from(Array(1000).keys()), item => item + 1);
// Results:
// Array of 1000 integers in ascending order, mapping with incremented values
// map x 386,175 ops/sec ±13.96% (91 runs sampled)
// reduce x 257 ops/sec ±1.34% (85 runs sampled)
// forEach x 193,596 ops/sec ±0.78% (92 runs sampled)
// for-loop x 190,224 ops/sec ±0.36% (96 runs sampled)
// for-loop in-place x 444,796 ops/sec ±0.61% (93 runs sampled)
// forEach in-place x 805,311 ops/sec ±19.99% (92 runs sampled)
// Fastest is forEach in-place
// Array of 1000 objects, mapping with merged key/value pair
// map x 48,774 ops/sec ±11.75% (86 runs sampled)
// reduce x 215 ops/sec ±2.39% (83 runs sampled)
// forEach x 45,489 ops/sec ±2.12% (92 runs sampled)
// for-loop x 48,263 ops/sec ±4.41% (93 runs sampled)
// forEach in-place x 3,440 ops/sec ±0.69% (90 runs sampled)
// for-loop in-place x 3,430 ops/sec ±1.31% (91 runs sampled)
// Fastest is for-loop
// Conclusions:
// If in-place mutation is off the table, `.map` is by far superior to the alternatives for simple transformations like incrementing numbers. If you allow for in-place mutation, forEach takes the cake, being roughly twice as fast as both `.map` and the in-place for loop.
// When the transformation requires creating a new object e.g. when merging a key/value pair into an object, everything is slower, but the for-loop is the fastest. The in-place `.forEach` and for-loop approaches are notably quite slow compared to in the previous case. I suspect this is because objects don't have fixed size, meaning replacing an object at a given index in an array full of objects is a more expensive operation than simply appending to a new array. We might see an increase in speed if we were simply overwriting a single key on the object rather than replacing the object with a new one in each iteration.
// In all cases, reduce is the slowest by a large margin. This would be because it has to recreate a new array in each iteration and populate it. No doubt in situations where it's cheaper to create the new value of the accumulator, the difference would not be as steep.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment