Perf difference between .set API and manual loop for ring buffer usage

Benchmark created on


Setup

const JS_BUFFER_SIZE = 256;
const WASM_BUFFER_SIZE = 320;
const SIMULATION_CYCLES = 100;

const testData = [];
for (let i = 0; i < SIMULATION_CYCLES; i++) {
  const buffer = new Float32Array(JS_BUFFER_SIZE);
  for (let j = 0; j < JS_BUFFER_SIZE; j++) {
    buffer[j] = Math.random();
  }
  testData[i] = buffer;
}

class TestRingBuffer {
  constructor(size, useNativeAPI = true) {
    this.buffer = new Float32Array(size);
    this.size = size;
    this.writePos = 0;
    this.readPos = 0;
    this.useNativeAPI = useNativeAPI;
  }
  
  getAvailableSpace() {
    return (this.readPos - this.writePos - 1 + this.size) % this.size;
  }
  
  getDataLength() {
    return (this.writePos - this.readPos + this.size) % this.size;
  }
  
  write(data) {
    const availableSpace = this.getAvailableSpace();
    if (availableSpace < data.length) {
      // Handle overflow - advance read position
      const samplesToAdvance = Math.max(data.length - availableSpace + 4, 4);
      const alignedAdvance = Math.ceil(samplesToAdvance / 4) * 4;
      this.readPos = (this.readPos + alignedAdvance) % this.size;
    }
    
    if (this.useNativeAPI) {
      // Native API approach
      if (this.writePos + data.length <= this.size) {
        this.buffer.set(data, this.writePos);
        this.writePos += data.length;
      } else {
        const partitionSizeFormer = this.size - this.writePos;
        const partitionSizeLatter = data.length - partitionSizeFormer;
        
        const dataViewFormer = new Float32Array(
          data.buffer, data.byteOffset, partitionSizeFormer
        );
        const dataViewLatter = new Float32Array(
          data.buffer, 
          data.byteOffset + Float32Array.BYTES_PER_ELEMENT * partitionSizeFormer,
          partitionSizeLatter
        );
        
        this.buffer.set(dataViewFormer, this.writePos);
        this.buffer.set(dataViewLatter, 0);
        this.writePos = partitionSizeLatter;
      }
    } else {
      // Manual loop approach
      for (let i = 0; i < data.length; i++) {
        this.buffer[this.writePos] = data[i];
        this.writePos = (this.writePos + 1) % this.size;
      }
    }
  }
  
  read(length) {
    const availableData = this.getDataLength();
    const samplesToRead = Math.min(length, availableData);
    
    if (samplesToRead === 0) {
      return new Float32Array(0);
    }
    
    if (this.useNativeAPI) {
      // Native API approach
      if (this.readPos + samplesToRead <= this.size) {
        const result = this.buffer.subarray(this.readPos, this.readPos + samplesToRead);
        this.readPos += samplesToRead;
        return result;
      }
      
      const result = new Float32Array(samplesToRead);
      const partitionSizeFormer = this.size - this.readPos;
      const partitionSizeLatter = samplesToRead - partitionSizeFormer;
      
      result.set(this.buffer.subarray(this.readPos, this.size), 0);
      result.set(this.buffer.subarray(0, partitionSizeLatter), partitionSizeFormer);
      
      this.readPos = partitionSizeLatter;
      return result;
    } else {
      // Manual loop approach
      const result = new Float32Array(samplesToRead);
      for (let i = 0; i < samplesToRead; i++) {
        result[i] = this.buffer[this.readPos];
        this.readPos = (this.readPos + 1) % this.size;
      }
      return result;
    }
  }
  
  reset() {
    this.writePos = 0;
    this.readPos = 0;
  }
}

let ringBuffer = null;

function startTest() {
	for (let cycle = 0; cycle < SIMULATION_CYCLES; cycle++) {
      ringBuffer.write(testData[cycle]);
    }
}


Test runner

Ready to run.

Testing in
TestOps/sec
Write - Native API (512)
ringBuffer = new TestRingBuffer(512, true);

startTest();
ready
Write - Manual Loop
ringBuffer = new TestRingBuffer(1024, false);

startTest();
ready
Write - Native API (1024)
ringBuffer = new TestRingBuffer(1024, true);

startTest();
ready
Write - Native API (2048)
ringBuffer = new TestRingBuffer(2048, true);

startTest();
ready
Write - Native API (4096)
ringBuffer = new TestRingBuffer(4096, true);

startTest();
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.