3

I'm looking for a performant way to remove and store elements from an array. I am trying to make an object pool to reduce garbage collections calls.

Just as .pop() and .unshift() remove elements from an array and return the value of that element, I'd like to be able to remove an element at a specific index, while storing it's value in a variable, and while not creating unnecessary arrays/objects.

.splice() removes the element at a specific index just fine, and stores that value in an array. I can access that, but the function itself creates a new array, which will eventually trigger the garbage collector.

.slice() has the same issue, a new array is created.

Is there a way to pull out and store a specific indexed element without the creation of a new array?

jackrugile
  • 1,653
  • 15
  • 25
  • If you don't want to amend the source array, use `var item = array[index]; delete array[index];`. You'll manually get rid of the element in the array setting its value to `undefined`. – VisioN May 06 '14 at 08:42
  • Your suggestion doesn't remove item from array, but rather changes that item to be undefined. see array length after delete – Armen May 06 '14 at 08:51
  • @ VisioN if a=[0,1,2,3,4,5,6,7,8,9] then on scratchpad in Firefox delete a[4] gives a=[0,1,2,3,,5,6,7,8,9] whereas I presume OP wants [0,1,2,3,5,6,7,8,9] – jing3142 May 06 '14 at 08:51
  • If the OP wants `[0,1,2,3,5,6,7,8,9]` then there is not other way than using `s(p)lice`. – VisioN May 06 '14 at 08:59
  • @ VisioN When a=[0,1,2,3,4,5,6,7,8,9] is it true that using var item = a[4]; a.splice(4,1); will create a new array? I thought it just altered the existing array. In scratchpad I get item=4 and a=[0,1,2,3,5,6,7,8,9] – jing3142 May 06 '14 at 09:05
  • 2
    @jing3142 What he means is that `Array.prototype.splice` will return a new array (of the removed elements). Which will trigger the GC later. OP's looking for a way to avoid that – Some Guy May 06 '14 at 16:38
  • 1
    Why not implement a splice that doesn't create a new array? – Esailija May 06 '14 at 16:52
  • @Esailija That might avoid triggering the GC, but it might also end up being slower in itself because it won't be optimized, right? – Some Guy May 06 '14 at 17:05
  • @SomeGuy it will not only be optimized but it will be much faster than any built-in function because you can avoid semantic complexity like holed arrays. – Esailija May 06 '14 at 17:08
  • @Esailija Can you provide an example of a splice implementation that doesn't create a new array? – jackrugile May 06 '14 at 19:42
  • @jackrugile look at https://code.google.com/p/v8/source/browse/trunk/src/array.js#762 , what do you need from splice? It does many things :) Do you just need to move _one_ element? How close is their position to the end (with a large enough pool size N, the O(1) of a linked list might be better than the O(N) of deleting from an array). – Benjamin Gruenbaum May 06 '14 at 19:46
  • 1
    @BenjaminGruenbaum linked lists perform so shit in javascript that you better have a huge list and lots of deletions in the middle before even considering it over a tight array :P related http://kjellkod.wordpress.com/2012/02/25/why-you-should-never-ever-ever-use-linked-list-in-your-code-again/ – Esailija May 07 '14 at 07:48
  • @Esailija which is why I said "with a large enough pool". Linked Lists perform poorly in any language, not just JavaScript. – Benjamin Gruenbaum May 07 '14 at 08:29

1 Answers1

4

This always removes one item at index, if you need to remove more than 1 consecutive items at a time, it would be more efficient to implement it to take a howMany argument and remove them in a batch instead of calling removeAt repeatedly.

function removeAt(array, index) {
    // Assumes array and index are always valid values
    // place validation code here if needed
    var len = array.length;
    // for example if index is not valid here, it will deoptimize the function
    var ret = array[index];
    for (var i = index + 1; i < len; ++i) {
        array[i - 1] = array[i];
    }
    array.length = len - 1;
    return ret;
}

Usage:

var a = [1,2,3,4,5]
var removed = removeAt(a, 2);
console.log(a);
// [1, 2, 4, 5]
console.log(removed);
// 3
Esailija
  • 138,174
  • 23
  • 272
  • 326
  • If Jack doesn't care about the order of the arrays, the function might be faster this way: `arr[index] = arr[0]; return arr.shift();` – Some Guy May 07 '14 at 07:50
  • @SomeGuy that doesn't make any sense, shift needs to move all elements in the array – Esailija May 07 '14 at 07:53
  • True. I guess I'm just giving native function optimizations too much credit – Some Guy May 07 '14 at 07:57
  • @SomeGuy yes you are, for example I have implemented a double-ended queue that is 4x faster than `.shift` even when the V8 trick is applied, and 10000000000x faster than `.shift` when the array is too large to apply the V8 trick. https://github.com/petkaantonov/deque#why-not-use-an-array – Esailija May 07 '14 at 08:00
  • Why are you caching the length for the loop? – Benjamin Gruenbaum May 07 '14 at 08:37
  • @BenjaminGruenbaum why not? – Esailija May 07 '14 at 08:44
  • It's an extra variable and as far as I know it doesn't help with performance (or readability) at all. So I was wondering why you added it – Benjamin Gruenbaum May 07 '14 at 08:52
  • cos the hoisting optimization is extremely unreliable – Esailija May 07 '14 at 08:54
  • 1
    `i < array.length` reads the length from memory for every iteration of the loop, where as `i < len` stores the length in register for the entire loop. However the optimization that would make these equal is extremely unreliable so might as well write `i < len` every time – Esailija May 07 '14 at 08:59
  • Thanks Esailija, a friend recommended a similar method and I am doing some testing with it now. Looks promising! Much appreciated. I'm always looking to increase performance for my HTML5 games. Would you recommend deque for any game uses? – jackrugile May 08 '14 at 04:38
  • Thankyou Esailija, this will save me a LOT of headaches! For those interested I have created a jsperf test for a simplified version of the removeAt function that doesn't return the removed item, and it is significantly faster than array.splice. https://jsperf.com/array-splice-vs-custom-garbage-free-alternative/1 – James Hill Feb 08 '19 at 16:47