Note: this is a cross-post from https://discourse.wicg.io/t/js-array-method-to-append-another-array-in-place/3831
We have experience writing a high-performance game engine in JavaScript. One problem we've come across is the most performant way to append one array to the end of another in place. (arr1.concat(arr2)
returns a new array; this is specifically for an in-place append that modifies arr1
.)
The obvious solution is arr1.push(...arr2)
, but this has a nasty gotcha: the spread passes the array as arguments on the stack, which means if arr2
is long enough, you reach implementation-defined maximum argument length limits. Commonly this is around 65k, but varies from browser to browser. Worse, since the limits are fairly high, writing this type of code at first looks like it works, but then you find out it's broken later on as your arrays get longer and reach limit (worse still, a non-standard limit).
The next obvious solution is to use a loop to call push()
, e.g.:
for (let i = 0, len = arr2.length; i < len; ++i)
arr1.push(arr2[i]);
The problem with this is it's significantly slower, to the point we've found a helper method using exactly this approach was coming up high in performance profiles. It makes sense why: the optimizer probably can't tell how long arr1
will end up, so as it keeps pushing it will have to keep reallocating the backing store to increase the array capacity, and every time it does that it will have to copy the entire array.
Switching to arr1.push(...arr2)
measurably increased performance - but then we have to impose an arbitrary hard-coded limit on how long arr2
is to use that fast-path, else fall back to the slow path push()
loop - and then we end up switching to a slow path in the most performance intensive case (with long arrays).
This is all pretty awkward and still doesn't achieve maximum performance with long arrays. It could be solved by adding a new array method, e.g.:
// Appends all of arr2 to the end of arr1 in-place
arr1.append(arr2);
This method meets all of the following requirements:
- Modify an existing array, instead of returning a new array
- Resize the backing store once, since the final size is known
- Preserve the internal element kinds (providing the appended array has a compatible internal type)
- Append any size array, without arbitrary/non-standard limits
As far as I am aware, it is not possible to achieve all those requirements using any other JavaScript workaround.