3

I accidentally compared a large array and a number with <, and JavaScript locked up for over 5 seconds. What is the expected behavior of this comparison? Is it iterating over the whole array? MDN didn't clarify the situation.

As a concrete example, this code snippet takes over 5 seconds to print done:

var m = [];
m[268435461] = -1;
console.log('start');
if (m < 0) { }
console.log('done');

7
  • 1
    You are assigning the 268435461th entry in a sparse array the value -1 - is that is what you wanted to do? Commented Mar 26, 2019 at 18:09
  • 5
    You've created a sparse array, which is stored differently from regular arrays to be more memory efficient. They're also slower. Commented Mar 26, 2019 at 18:09
  • 3
    The comparison is slow because m is first coerced to a (huge) string. let x = []; x[3] = -1; '' + x gives ,,,-1. Your m is coerced to 268435461 commas followed by -1. At least on modern Chrome, creating a sparse array takes a few milliseconds, and is not the reason your example is slow. Commented Mar 26, 2019 at 18:20
  • Why would it coerce it to a string instead of a number? Commented Mar 26, 2019 at 18:24
  • 1
    Long story short, (loosely) comparing an object to a number invokes the ToPrimitive procedure, which invokes valueOf, doesn't get back a primitive, and invokes toString. Commented Mar 26, 2019 at 18:31

1 Answer 1

3

Javascript "arrays" (those with Array prototype, not typed arrays), are just objects, therefore this

var m = [];
m[268435461] = -1;

is exactly the same as

var m = {
    "268435461": -1
}

except that in the first case, m has the Array prototype and a special length property.

However, methods defined in Array.prototype (like forEach or join) are trying to hide that fact and "emulate" sequential arrays, as they exist in other languages. When iterating their "this" array, these methods take its length property, increase the loop counter from 0 upto length-1 and do something with the value under the key String(i) (or undefined if there's no such key)

// built-in js array iteration algorithm

for (let i = 0; i < this.length - 1; i++) {
     if (this.hasOwnProperty(String(i))
         do_something_with(this[String(i)])
     else
         do_something_with(undefined)

Now, length of an array is not a number of elements in it, as the name might suggest, but rather the max numeric value of its keys + 1, so in your case, length will be 268435462 (check it!)

When you do m < 0, that is, compare a non-number to a number, JS converts them both to strings, and Array.toString invokes Array.join, which, in turn, uses the above loop to convert elements to strings and insert a comma in between:

// built-in js Array.join algorithm

target = '';

for (let i = 0; i < this.length - 1; i++) {
    let element = this[String(i)]

    if(element !== undefined)
        target += element.toString()

    target += ','
}

Illustration:

m  = [];
m[50] = 1;
console.log(m.join())

This involves lots of memory allocations, and that's what is causing the delay.

(After some more testing, the allocation are not the deciding factor here, "hollow" loops will cause the same slowdown:

console.time('small-init')
var m = [];
m[1] = -1;
console.timeEnd('small-init')

console.time('small-loop')
m.forEach(x => null)
console.timeEnd('small-loop')

console.time('big-init')
var m = [];
m[1e8] = -1;
console.timeEnd('big-init')

console.time('big-loop')
m.forEach(x => null);
console.timeEnd('big-loop')

That being said, I don't think modern JS engines are that silly, and implement iterations exactly as described above. They do have array-specific optimizations in place, but these optimizations are targeted at "good" sequential arrays, and not at bizarre edge cases like this. Bottom line: don't do that!

Sign up to request clarification or add additional context in comments.

12 Comments

Arrays are Vectors or ArrayLists exactly like they exist in other languages. Sure, JS has a unified way of accessing elements in the same way as properties, but I wouldn't say that JS aren't "sequential". Imo you should just drop the first part of your answer, it doesn't seem relevant to the question.
@Bergi: last time I checked, Ecma spec didn't mention Vectors or ArrayLists, it does, however, say that "A[rrays]'s essential internal methods... [are] the default ordinary object definitions"
The ECMA spec doesn't detail memory layout of objects and arrays at all, so it's irrelevant. In every JS engine implementation I've encountered so far, arrays and objects were handled very differently though.
@Bergi: so, why exactly is this code slow? An array optimization should have kicked in and realized that there's only one element in the array, not a gazillion...
It probably did realise that there are no elements in the array. But as you correctly explained, it still has to build a 268435463-character string, which is the slow thing.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.