Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does spread operator affect performance?

I am considering the below two approaches for building an array of objects:

Approach 1 (list all properties, even if duplicated among objects):

const employees = [
  {
    company: 'ABC',
    country: 'IN',
    zip: 123,
    employeeId: 123,
    employeeName: 'p'
  },
  {
    company: 'ABC',
    country: 'IN',
    zip: 123,
    employeeId: 456,
    employeeName: 'q'
  },
  {
    company: 'ABC',
    country: 'IN',
    zip: 123,
    employeeId: 789,
    employeeName: 'r'
  }
];

Approach 2 (avoid duplication with the spread operator):

const commonParams = {
  company: 'ABC',
  country: 'IN',
  zip: 123
};

const employees = [
  {
    ...commonParams,
    employeeId: 123,
    employeeName: 'p'
  },
  {
    ...commonParams,
    employeeId: 456,
    employeeName: 'q'
  },
  {
    ...commonParams,
    employeeId: 789,
    employeeName: 'r'
  }
]

Approach 2 is more succint, and adding a new property that is common to all array elements would be much easier (and less prone to errors).

However, in case of a large commonParams object, does approach 2 (using the spread operator) affect performance as compared to approach 1?

Would the spread operator loop through each of the properties of the commonParams object for each of the objects in the employees array?

like image 757
Boney Avatar asked Apr 25 '19 06:04

Boney


People also ask

Is the spread operator faster?

While spreading is slower you're still getting a lot of ops per second. It's just that if you do it the boring way you'll get a lot more ops per second.

What is the purpose of spread operator?

The JavaScript spread operator ( ... ) allows us to quickly copy all or part of an existing array or object into another array or object.

What is the time complexity of spread operator?

Answer. It's O(n) .

Is spread faster than Concat?

As you can see, spread is 50% faster for smaller arrays, while concat is multiple times faster on large arrays.


3 Answers

Yes, spreading a variable which refers to an object into another object requires the interpreter to look up what the variable refers to, and then look up all the enumerable own properties (and the associated values) of the object that gets spreaded so as to insert into the new object. This does indeed take a bit of processing power.

But, on modern computers, and on modern JS engines, the processing power required is next to nothing; what does it matter, when millions of instructions can be processed each second? A handful of key-value pairs is nothing to worry about.

Unless you've identified that you're spreading an object with tons of key-value pairs, and it's actually causing a performance bottleneck, it would be a better idea to avoid premature optimization and aim to write clean, readable code instead (which may well invoke using spread syntax often). For a large employees array, the second approach is more readable than the first.

(though, you also might consider using .map, to keep the code even DRY-er:)

const employeesInitial = [
  {
    employeeId: 123,
    employeeName: 'p'
  },
  {
    employeeId: 456,
    employeeName: 'q'
  },
  {
    employeeId: 789,
    employeeName: 'r'
  }
];
const employees = employeesInitial.map((obj) => ({ ...obj, ...commonParams }));
like image 170
CertainPerformance Avatar answered Sep 22 '22 19:09

CertainPerformance


The cost of spreading is significant. We're talking 2 orders of magnitude here.

const { x, y } = z

z = { x, y: y + 1 } // faster
z = { ...z, y: y + 1 } // slower

While they both accomplish similar things they are very different in their performance characteristics. But it will depend, if and how your JavaScript is transpiled.

For example, Babel will actually emit something which is similar to the faster variant if you target ES2015 but if you target ES2017 you'll get the slower variant, as-is. If you target ECMASCRIPT_2018 with the Google Closure Compiler you get the slower variant. With the TypeScript compiler you end up with twice as many objects because it does nested Object.assign calls.

While spreading is slower you're still getting a lot of ops per second. It's just that if you do it the boring way you'll get a lot more ops per second.

I put together a jsperf example to illustrate this.

https://jsperf.com/the-cost-of-spreading/1

If you have a hot code path that does spreading, consider direct construction. Otherwise, don't bother.

like image 45
John Leidegren Avatar answered Sep 21 '22 19:09

John Leidegren


Time to run second approach will be longer (even if very little on modern computers) as interpreter has to iterate over keys of commonParams and copy them to each object.

Wrote a benchmark to find difference which is almost zero for small objects.

function runFirstApproach(){
  const employees1 = [
    {
      company: 'ABC',
      country: 'IN',
      zip: 123,
      employeeId: 123,
      employeeName: 'p'
    },
    {
      company: 'ABC',
      country: 'IN',
      zip: 123,
      employeeId: 456,
      employeeName: 'q'
    },
    {
      company: 'ABC',
      country: 'IN',
      zip: 123,
      employeeId: 789,
      employeeName: 'r'
    }
  ];
}

function runSecondApproach() {
  const commonParams = {
    company: 'ABC',
    country: 'IN',
    zip: 123
  };

  const employees2 = [
    {
      ...commonParams,
      employeeId: 123,
      employeeName: 'p'
    },
    {
      ...commonParams,
      employeeId: 456,
      employeeName: 'q'
    },
    {
      ...commonParams,
      employeeId: 789,
      employeeName: 'r'
    }
  ]
}

function runBenchmarkWithFirstApproach(){
  console.log("Avg time to run first approach -> ", getAvgRunTime(runFirstApproach, 100000))
}

function runBenchmarkWithSecondApproach(){
  console.log("Avg time to run second approach ->", getAvgRunTime(runSecondApproach, 100000))
}

function getAvgRunTime(func, rep){
  let totalTime = 0;
  let tempRep = rep;
  while(tempRep--) {
    const startTime = Date.now();
    func();
    const endTime = Date.now();
    const timeTaken = endTime-startTime;
    totalTime += timeTaken;
  }
  return totalTime/rep;
}

runBenchmarkWithFirstApproach();
runBenchmarkWithSecondApproach();
like image 22
Anurag Awasthi Avatar answered Sep 21 '22 19:09

Anurag Awasthi