Performance of JavaScript object reduce

Performance of JavaScript object reduce

Just a short post to look at assumptions. A few weeks ago was having one of those classic debates with a co-worker about the performance of object construction. Since with ES6 it is very easy to write:

1const obj = data.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {});

I had internally always made the assumption that JavaScript's runtime was making some tail call style optimizations on object reduce. But, after the discussion I realized that you should measure and not assume.

So measure we did, only to find that while it's short in syntax it's slow in performance:

Function 10 items 100 items 1000 items 10000 items
reduceHead 0.094276 1.056871 113.687105 11013.714067
reduceTail 0.117997 1.777858 110.682865 11794.382281
reduceAssign 0.090573 0.161337 0.420563 2.184986

This is on the 8.9.0 node runtime. All times are in 'ms'

What you can see is this is pretty close to a exponentail growth based on the number of items. Sure it's fast enough if it's a small object (~10 items) but, very quickly grows if it's anything of size.

The test program (run at each of the different input counts)

 1const {
 2  performance,
 3  PerformanceObserver
 4} = require('perf_hooks');
 5
 6/*
 7** Construct some test data
 8*/
 9const data = [];
10
11for (let i = 0; i < 1000; i ++) {
12  data.push([String(i), i]);
13}
14
15/*
16**  Test variations
17*/
18function reduceHead() {
19  return data.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {});
20}
21
22function reduceTail() {
23  return data.reduce((acc, [k, v]) => ({ [k]: v, ...acc }), {});
24}
25
26function reduceAssign() {
27  return data.reduce((acc, [k, v]) => {
28    acc[k] = v;
29    return acc;
30  }, {});
31}
32
33/*
34**  Performance hooks
35*/
36const obs = new PerformanceObserver((list) => {
37  const entries = list.getEntries();
38  console.log(entries[0].name, entries[0].duration, 'ms');
39});
40
41const wrapHead = performance.timerify(reduceHead);
42const wrapTail = performance.timerify(reduceTail);
43const wrapAssign = performance.timerify(reduceAssign);
44
45obs.observe({ entryTypes: ['function'] });
46
47wrapHead();
48wrapTail();
49wrapAssign();