Skip to content

Instantly share code, notes, and snippets.

@customcommander
Last active December 18, 2023 11:13
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save customcommander/97eb4b3f1600773db59406d39f3f9cd7 to your computer and use it in GitHub Desktop.
Save customcommander/97eb4b3f1600773db59406d39f3f9cd7 to your computer and use it in GitHub Desktop.
On using assignment over spread to achieve performance

Spread vs assign

Abstract

In this article I compare the performance of the spread operator ... and the performance of the assignement operator = in the context of data transformation.

I show that using the spread operator isn't a trivial choice to make and I suggest that immutability and mutation don't have to be mutually exclusive. I also show how one-liner functions can be enriched with the comma operator ,.

What are we measuring?

We're measuring the performance of transforming an array of records into a map e.g.,

From:

[
  [
    "bda579a3-c259-4529-b845-12644a22cdbc",
    "Jeff_Thompson26@example.org"
  ],
  [
    "389c1582-a694-41a2-b867-2f6fd05e7c91",
    "Lilly.Lockman@example.com"
  ],
  [
    "4b204b42-13f5-487e-929f-0d64ee1c122b",
    "Marianna62@example.com"
  ]
]

To:

{
  "bda579a3-c259-4529-b845-12644a22cdbc": "Jeff_Thompson26@example.org",
  "389c1582-a694-41a2-b867-2f6fd05e7c91": "Lilly.Lockman@example.com",
  "4b204b42-13f5-487e-929f-0d64ee1c122b": "Marianna62@example.com"
}

Generating records

We'll be using this script to generate an array of records:

// generate-records.js

const { v4: uuid } = require('uuid');
const faker = require('faker');

const records = [];
for (let i = 0; i < process.env.RECORD; i++) {
  records.push([
    uuid(),
    faker.internet.exampleEmail()
  ]);
}

console.log(JSON.stringify(records, null, 2));

To create a JSON file of 100 records we'll invoke the script as follow:

RECORD=100 node generate-records.js > records.json

Our spread-based transformation

// to-map-spread.js

const records = require('./records');

const to_map =
  rs =>
    rs.reduce
      ( (o, [k, v]) => ({...o, [k]: v})
      , {}
      );

to_map(records);

Our assignment-based transformation

// to-map-assign.js

const records = require('./records');

const to_map =
  rs =>
    rs.reduce
      ( (o, [k, v]) => (o[k] = v, o)
      , {}
      );

to_map(records);

It is worth noting that the comma operator , is used to chain expressions and return the last expression. It is equivalent to:

(o, [k, v]) => {
  o[k] = v;
  return o;
}

Measuring performance

The two transformation methods will be compared against datasets of different sizes:

  1. 100 records
  2. 500 records
  3. 1000 records
  4. 2000 records
  5. 5000 records
  6. 10000 records

We'll use this script to automate the generation of records and each run:

#!/bin/sh

run () {
  RECORD=$1 node generate-records.js > records.json
  echo "$1 - spread"
  time node to-map-spread.js
  echo "$1 - assign"
  time node to-map-assign.js
}

run 100
run 500
run 1000
run 2000
run 5000
run 10000

Only the real time will be taken into account.

Results

Here are the results for one run on my MacBook Pro using Node.js 12
(2.5 GHz Intel Core i7 ∙ 16 GB 1600 MHz DDR3 ∙ macOS 10.14.6 (18G4032))

Records Spread Assign
100 42ms 41ms
500 62ms 41ms
1000 151ms 41ms
2000 969ms 41ms
5000 6.163s 49ms
10000 24.758s 56ms

Conclusions & further thoughts

While the approach to measuring performance could definitely do with more scientific rigour, the results should compel developers to either proceed with caution or investigate further.

The spread operator ... is a popular choice to create one-liner functions but it seems to quickly perform poorly in an iterative process. In such scenario the use of the spread operator to provide mutation-free computation should be questioned. Consider this contrived example:

const map = xs.reduce((acc, x) => ({...acc, [x]: true}), {});
//                                                       ^^

Uncontrolled access to shared state can lead to complex bugs which is why immutability is so dear to functional programmers. However immutability isn't only achieved through the cloning of data, in fact this is most likely to be a naive approach to it. Immutability is a quality of a data structure not a process per se.

In this particular case the initial value of the reducing function is safe to mutate. The spread operator offers no additional protection against side effects.

@dipenparmar12
Copy link

Good one..

@CrossEye
Copy link

CrossEye commented Feb 8, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment