DEV Community

Cover image for Assigning [ ] performs better than Array(n) - Reports attached.
Vani Shivanand
Vani Shivanand

Posted on

Assigning [ ] performs better than Array(n) - Reports attached.

Here is a small snippet that was measured using jsperf sit

Alt Text

That means Array(n) is a lot slower than [].

What might be the reason?

In the background there are different types of arrays though for a developer it looks like there is only one Array type.

For the sake of scope, two types can be discussed here.

  • Holey element type
  • Packed element type

If we initialize the array with a size, it create an array with Holey element type. Otherwise Packed element type. There are different cases when the javascript engine converts packed element array type into holey.

One of it is a delete operation. Another case is,

const arr = [];
arr[4] = 10;

In the above snippet, though in the first line it creates a packed array. Since there are no elements on index 0,1,2 & 3, the javascript engine converts it into holey array.

That was background. What might be the reason for performance difference?

For a packed array, all that the javascript engine needs to do to find an element is,

  • To check if the index is in the range (zero to arr.length)
  • return value if hasOwnProperty of that index is true (as array is stored in exact object format with index as key)

What if it is a holey array?

  • To check if the index is in the range
  • hasOwnProperty of that index
  • hasOwnProperty of Array.prototype
  • hasOwnProperty of Object.prototype
  • and the same with any/all extended entities

Because in a holey array, the javascript engine can't decide just based on the range (zero to arr.length)

That's the reason, any operation on holey array takes several more steps than packed array causing performance difference.

Reference : https://www.youtube.com/watch?time_continue=445&v=m9cTaYI95Zc&feature=emb_title

Thanks for reading!

Top comments (22)

Collapse
 
miketalbot profile image
Mike Talbot ⭐

Hey there's a problem with your jsperf code. The first example ends up with an array containing 3999 items not 2000. So all of the rest of it is skewed by that. The fact is the best performance is the other way around:

jsperf.com/test-assign-vs-push/1

Collapse
 
svaani profile image
Vani Shivanand • Edited

That's right. I'm sorry that I didn't observe that. But as per the explanation holey array should be slower unless there's a trade off on memory allocation on a repeated basis. I'll get back on this. Thanks for correcting!

Update

I have updated the image with the latest example. Please check

Alt Text

You may argue that the length of array is 2000 in the other case. But in reality there are no 2000 elements in the memory.

Also, if we do assign 2000 memory then we will be doing initial 20 operations which are slower. But yes, there's a trade-off in memory allocation performance

Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

Well you are doing an array.join on 2000 elements in the 2nd example and only 20 in the first. The fact that they are undefined and therefore add up to very little output doesn't change the fact it has to process it with the join. It's your innerHTML that is taking the time now...

Remove that inconsistency (and make it so there's enough data to really compare) jsperf.com/assign-vs-push-d2/1

I guess if you are saying that you have to account for empty elements in a bigger array, then I get that - over-allocating an array would certainly cause problems as everything will have to deal with the empty space when running across the whole array. The point I'm making is that it's more performant for allocation than pushing on-demand, individual item lookup performance remains optimal, but I agree that whole comprehensions are slower if the empty space is meaningless.

Thread Thread
 
svaani profile image
Vani Shivanand

Please refer,

dev.to/svaani/comment/12443

As you scroll down the comments, you will see the same discussion that went through. Looks like, chrome team has recently has optimized on holey arrays. Firefox gives different results even with the link that you have provided.

Collapse
 
dry profile image
Hayden Mankin

I noticed you updated your example for accuracy. While the statements you are making are true, your example is still not entirely representative of this fact. The initialization of the array is one of the more costly steps in your example, especially one of size 2000

array init - performance

It might make sense to use jsperfs setup function to create them outside the timed section.

/* setup */
let holey = new Array(2000);
let packed = [];

array insertion - performance

Notice how this shows the holey array as faster? This is because it still isn't a fair test, the packed array needs to grow to accommodate insertions using push, so we should be testing with a packed array the same size as the holey array.

/* setup */
let holey = new Array(2000);
let packed = [...new Array(2000)];

marginal difference in arrays

I'm not seeing much of a difference between a packed array and holey array using the constructor, so I tried a different way of making a holey array and saw a major drop in performance.

/* setup */
let holey = [];
holey[1999] = 2;
let packed = [...new Array(2000)];

holey vs packed performance

This last example shows the effects of holey arrays. I'm unable to test this in a way that shows a performance benefit to not using the array constructor, I could be wrong about this in some way, but the benefits seem to be marginal at best. The holey arrays created using the other method do seem to act different than ones created using the constructor nonetheless.

Collapse
 
svaani profile image
Vani Shivanand • Edited

In most of the examples that you have quoted, you are checking only the assignment part but not retrieve part.

Consider this example, where join triggers retrieve part also. You will get the results otherwise,
Alt Text

Only assignment case

In the same example as you have given in the first snippet also gives the equal results if we don't use push. push tend to take a longer time but I still need to get a better clarity on to how push takes longer.
Alt Text

Collapse
 
dry profile image
Hayden Mankin

I'm guessing you ran this test: jsperf.com/test-assign-vs-push/12

The join method is taking so long because it is running on an array with length 2000 compared to the array of length 20, it's not a fair comparison. Despite those being 'empty' they aren't ignored by the join method.

Try running:

console.log((new Array(10)).join("test "))

It still prints test test test test test test test test test despite the empty entries

If you change new Array(2000) to new Array(20) that difference becomes negligible.

Thread Thread
 
svaani profile image
Vani Shivanand • Edited

In that case, let's have this code in the setup,

  let holey = new Array(2000);
  let packed = [];
  for(let i = 0; i < 20; i++) packed[i] =2
  for(let i = 0; i < 20; i++) holey[i] = 2
  for(let i = 20; i < 2000; i++) packed[i] =undefined

And this is the result,
Alt Text

Now both the arrays are of size 2000 but the packed is not holey array.

That brings another loophole in holey arrays

In holey array, even though we have not assigned any values to the array, just because of allocation, the javascript engine is trying to hit those elements.

When we use an array with new Array(2000), would we not tend to use join,reduce,map and more?

Thread Thread
 
dry profile image
Hayden Mankin

jsperf.com/test-assign-vs-push/23 looks to be the same as what you are using, however when I run it I get much different performance results:

holey vs packed

A very negligible difference.

Also speaking to your note on javascript trying to hit those empty spots, this is actually very inconsistent in javascripts implementation. Some methods skip the holes entirely, others use them.

for example, map:

let arr = new Array(10);
arr[5] = 10;
let arr2 = arr.map(n => {
console.log('test');
return n * 2;
});
console.log(arr2);

Interestingly the console output is only:

test
[empty × 5, 20, empty × 4]

meaning the callback was only run for the one index that had a value.

Thread Thread
 
svaani profile image
Vani Shivanand

I think that's because chrome team is trying optimize the holey arrays too.

Thread Thread
 
dry profile image
Hayden Mankin

You are correct, I just tested it in firefox and got the same results as your example.

Thread Thread
 
svaani profile image
Vani Shivanand • Edited

Nice discussion thread. Thank you, Hayden Mankin!

Thread Thread
 
dry profile image
Hayden Mankin

Yes! this was really interesting to explore. I hadn't even considered the differences in browsers implementations.

I still think it would be best to modify your post to show tests using the setup area and similarly sized arrays as you have here for full accuracy.

Thread Thread
 
svaani profile image
Vani Shivanand • Edited

May be not for now!

Though it looks like chrome is working on holey array performance, they haven't given a heads-up yet on safe usage of holey array.

You may want to go through it, It is a video done by one of v8 team member.
youtube.com/watch?time_continue=44...

Thread Thread
 
dry profile image
Hayden Mankin

I think there may have been a misunderstanding, I don't see why you wouldn't modify your post.

I haven't once argued that holey arrays are good or that there are no drawbacks (I even said in my original reply: "While the statements you are making are true, your example is still not entirely representative of this fact"). This whole time I've just been trying to point out the problems with the testing method. All I'm suggesting is utilizing the startup section of jsperf for a more honest test, like shown in jsperf.com/test-assign-vs-push/23

Currently your post is using a test where the main performance difference is the initialization of a large array and then joining said 2000 element array. compared to initializing an empty array and joining 20 elements.

Your most recent example is a much better showcase of the drawbacks of holey arrays.

 
miketalbot profile image
Mike Talbot ⭐

Reversing the order of the tests reveals the opposite answer - I fear this is a jsPerf limitation. But again you have now made a weird array by adding values and not pushing for packed - so perhaps it's also a browser implementation of that which is giving you amazingly dramatically different results. Without the actual perf I can't see but 94% slower seems just very odd and certainly not inline with the one I link below in either case.

Screenshot

jsperf.com/testh-v-p

Thread Thread
 
svaani profile image
Vani Shivanand • Edited

I already mentioned about chrome vs firefox. Please go through the entire thread before commenting.

Also, don't forget to watch the video that is posted at the end of article. It is by v8 team member. That should answer most of your questions if you won't trust on random tests.

I won't be able to respond on this post further if the video provided at the end of post is not watched. Thanks!

Collapse
 
wulymammoth profile image
David

What about Array.prototype.fill?

Collapse
 
supermario_ai profile image
SuperMario

@Vani - Thoughts on the comments below?

If they are correct, consider editing your post for accuracy please.

We want accurate metrics on dev.to so all can benefit.

💯❤️

Collapse
 
svaani profile image
Vani Shivanand • Edited

Alt Text

This is an updated example

Collapse
 
supermario_ai profile image
SuperMario

Dope! ❤️💯

Collapse
 
mxldevs profile image
MxL Devs

Seems the issue is the way the array was instantiated. What if you did array(0) or something