Home > Mobile >  Pushing an element to every fourth place of a Float32Array efficiently in JS/TS
Pushing an element to every fourth place of a Float32Array efficiently in JS/TS

Time:10-21

I want to push a 1 to every fourth element of a Float32Array. My current solution is to convert said Float32Array into an array, iterating through it and applying .splice on every fourth element like so (I also convert the array to a buffer afterward but that is not relevant for this question):

function pushToEveryFourthAndConvertToBuffer(floatArray: Float32Array): Buffer {
  const array: number[] = [...floatArray];
  for (let i: number = 3; i <= array.length; i  = 4) array.splice(i, 0, 1);
  return Buffer.from(array);
}

while this works, it is not performant at all. My Float32Array has 660000 entries (thus, the returned buffer has 880000) and this function gets called with about 100 different Float32Arrays. If I remove the for-loop from the function, it runs much faster so I can be very sure my method of applying the elements is causing the bottle neck.

Is there a more efficient way or does my CPU just have to suffer and do I have to wait really long since I don't want to implement a multi-threaded solution?

Edit: I'll provide some input and expected output here to clarify my question:

new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0]); // ← input
new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]); // ← output

new Float32Array([0.2321, 0.294, 0.1246, 0.9352, 0.87423, 0.11]); // ← input
new Float32Array([0.2321, 0.294, 0.1246, 1, 0.9352, 0.87423, 0.11, 1]); // ← output

CodePudding user response:

A non concurrent way to do this might be something like this

const initial_array = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16];

//  from now on we only work with Float32Array. No conversion until the end
const old_floats = new Float32Array(initial_array);

//  pre-allocate your new Float32Array because you know how big it needs to be
const new_floats = new Float32Array( Math.ceil(initial_array.length * 5 / 4) );

//  fill it with ones
new_floats.fill(1, 0);

//  for performance, simple loops are better than array methods
for (let i=0; i<old_floats.length/4; i  ) {
  let read_start = i*4;
  let read_end = read_start 4;
  let write_start = i*5;
  let four_elements = old_floats.slice(read_start, read_end);
  new_floats.set(four_elements, write_start);
}

//  now let's convert back to numbers
console.log(new_floats);
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

For a concurrent way I would create 5 asynchronous functions. Function one would take every 4th value, function two every 4th 1, function 5 would simply inject "1" in every 5th value of the new array.

At the end, you would bring them together with Promise.all()

CodePudding user response:

Is there a reason you want to use array.splice? Compared to other array operations splice is very slow.

You can perform this same process by creating a new array with the expected size of your current one, and then assigning the values from the old array.

function fillArray(floatArray) {
  const array = [];
  array.length = Math.floor(floatArray.length * 4/3 );

  for (let index = 0, negativeIndex = 0; index < array.length; index  ) {
    if ((index   1) % 4 === 0) {
      array[index] = 1;
      index  ;
      negativeIndex--;
    }
    array[index] = floatArray[index   negativeIndex];
  }
  return Buffer.from(array);
}
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Running this via jsbench in my browser shows that your splice version is 3 to 8 ops/sec, while my version is 1,500 to 1,600 ops/sec. (jsbench doesn't support buffers so you have to skip that last step)

If you want simpler code you could use array.push instead of defining the array length and keeping track of the negativeIndex, but that only performs 500 to 600 ops/sec and you were specifically looking for efficiency.

  • Related