Number[] vs buffer performance

I need to store 16 values in a class, representing a 4x4 grid of numbers. Initially I was using number but I know that’s slow. Converting to number and just doing index = 4 * y + x is easy enough, but I was wondering if I could make it faster.

I did some testing with a buffer, and it seems to be significantly slower. I tried both Float32LE and Float32BE and there didn’t seem to be much difference. I know I could also do something like public v00: number, v01: number ... v32: number, v33: number but that would be a pain to work with and loop through. Am I using buffers or getting and setting numbers incorrectly? Also, is this way of benchmarking at all accurate? Thanks.

Test code:

// initial setup, doesn't count for performance
// set up 16 element 1D array
let myArr: number[] = [];
for (let i = 0; i < 16; i++) {
    myArr.push(i);
}
// 4 bytes per float
let myBuf = Buffer.create(16 * 4);
let format = NumberFormat.Float32LE;

// actual test
let samples = 10;
let totals = [0, 0];
let n = 100000;

// do 2 sets and 1 get n times to each
for (let s = 0; s < samples; s++) {
    pause(1);
    let t0 = game.runtime();
    for (let i = 0; i < n; i++) {
        let index = i % 16;
        myArr[index] = i * 3.0;
        myArr[index] = myArr[index] / 2.2;
    }
    pause(1);
    let t1 = game.runtime();
    totals[0] += t1 - t0;
}

for (let s = 0; s < samples; s++) {
    pause(1);
    let t0 = game.runtime();
    for (let i = 0; i < n; i++) {
        let index = 4 * (i % 16);
        myBuf.setNumber(format, index, i * 3.0);
        myBuf.setNumber(format, index, myBuf.getNumber(format, index) / 2.2);
    }
    pause(1);
    let t1 = game.runtime();
    totals[1] += t1 - t0;
}

let averages = [totals[0] / samples, totals[1] / samples];
game.splash("arr: " + averages[0] + "ms", "buf: " + averages[1] + "ms");
2 Likes

don’t put pauses into benchmarking code; use control.millis() instead of game.runtime() to get a more accurate value. game.runtime() is tied to the current frame which is why the pauses were causing it to change.

i would expect arrays to be faster, especially in the browser. buffers need to be packed/unpacked whenever numbers are read or written which is all overhead.

the place where buffers perform significantly better than arrays is in memory/code size. i expect they also perform a lot better in terms of allocation time for statically declared arrays.

for example:

// this requires an array to be allocated into memory and code
// to be generated for pushing each of the values into the array
let x = [1, 2, 3, 4]

// this requires no extra code generation and takes up no RAM if
// the buffer is read-only. writing to the buffer will move it into
// memory
let x = hex`1234`

for floating point numbers specifically, i think there isn’t much benefit to using buffers (unless they are read-only).

3 Likes

oh also, are you doing this benchmarking on hardware or in the browser?

2 Likes

I had been doing benchmarking in browser. I’ve swapped over to control.millis(), thanks for the info. I’m doing a lot of reading and writing so unfortunately a buffer won’t work here.

One thing I found was tuple types, which performed similarly to an array in testing but shouldn’t have the memory overhead. Something like:

type arr4x4 = [
    number, number, number, number,
    number, number, number, number,
    number, number, number, number,
    number, number, number, number
];

let myArr: arr4x4 = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];

Although to avoid errors I had to do (myArr as number[])[index].
One other thing that worked was:

let myArrBase: arr4x4 = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
let myArr: number[] = myArrBase;
myArr[index] //works fine

Though I’m not 100% sure how having a number[] point to a tuple works.
Is this solution worth pursuing, or should I just go for dynamic arrays and deal with higher memory use?

Did some more experimenting with tuples. There was no difference in performance time wise, and it led to some unexpected behaviour. Having an array point to a tuple, or just casting the tuple to number[] allows addition of extra items. From what I can tell tuples are just arrays with limitations, and aren’t actually static. Just using arrays seems like best approach for now.
Strange tuple code:

let myTup: [number, number] = [0,0];
let myArr: number[] = myTup;

myArr.push(4);
console.log(myTup.length) //prints 3
console.log(myTup[2]) // throws error

Similarly:

let myTup: [number, number] = [0, 0]
(myTup as number[]).push(5);
1 Like