Populating a pre-allocated array slower than a pushing to a regular array?

henryjw

Henry Williams

Posted on July 19, 2020

Populating a pre-allocated array slower than a pushing to a regular array?

For no good reason, I got the urge to do a performance comparison between populating an array by pushing to it vs writing to a buffer. Then, to make things more interesting, I decided to add a static array and a pre-allocated standard array.

Let's just say the results were not what I expected.

Experiment

Populate the 4 data structures by adding 10^8 elements to each and comparing the time it took for each of them.

Candidates

  • Static array - populated by writing directly to the index
  • Pre-allocated dynamic array - array initialized to hold all elements and then populated by setting elements for each index
  • Buffer - populated by writing directly the offset. Should be similar to writing to an index, but there might be some internal overhead
  • Array - empty array populated by pushing elements to it

Expected results (from fastest to slowest)

  1. Static array
  2. Pre-allocated array
  3. Buffer
  4. Array

Actual results (from fastest to slowest)

  1. Static array (228.039ms)
  2. Buffer (1.135s)
  3. Array (2.545s)
  4. Pre-allocated array (6.062s) (Why so slow???)

What I don't understand is why the pre-allocated array performed so poorly. I would expect its performance to be on par with a static array. I definitely didn't expect it to be outperformed by an array built by pushing elements into it.

Code

const NUMBER_OF_ELEMENTS = 10**8
const ELEMENT_LEN_BYTES = 4

const array = []

console.time('array')

for (let i = 1; i <= NUMBER_OF_ELEMENTS; i++) {
    array.push(i)
}

console.timeEnd('array')

const preAllocatedArray = new Array(NUMBER_OF_ELEMENTS)

console.time('pre-allocated array')

for (let i = 1; i <= NUMBER_OF_ELEMENTS; i++) {
    preAllocatedArray[i - 1] = i
}

console.timeEnd('pre-allocated array')

const intArray = new Uint32Array(NUMBER_OF_ELEMENTS)

console.time('int array')

for (let i = 0; i < NUMBER_OF_ELEMENTS; i++) {
    intArray[i] = i + 1
}

console.timeEnd('int array')


const buffer = Buffer.alloc(NUMBER_OF_ELEMENTS * ELEMENT_LEN_BYTES, 0)

console.time('buffer')

for (let i = 1, offset = 0; i <= NUMBER_OF_ELEMENTS; i++) {
    offset = buffer.writeUInt32BE(i, offset)
}

console.timeEnd('buffer')

// Results:
// array: 2.545s
// pre-allocated array: 6.062s
// int array: 228.039ms
// buffer: 1.135s
Enter fullscreen mode Exit fullscreen mode

Edit: It looks like the V8 engine's optimizations favor .push() over direct index assignment. The findings for Chrome in [this (ancient) article] are consistent with my results on Edge, Chrome, and Nodejs; all of which run on top of the v8 engine.

Thanks @alain Van Hout for sharing the link in the comments.

If anyone has any ideas how those optimizations are performed, please do share 🙂

💖 💪 🙅 🚩
henryjw
Henry Williams

Posted on July 19, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related