Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Buffer.(read|write)(U)Int(LE|BE)(8|16|32) #2

Open
ronag opened this issue Nov 10, 2022 · 6 comments
Open

Buffer.(read|write)(U)Int(LE|BE)(8|16|32) #2

ronag opened this issue Nov 10, 2022 · 6 comments
Assignees

Comments

@ronag
Copy link
Member

ronag commented Nov 10, 2022

Just adding something that I've been looking at which could maybe be improved.

Using Node Buffer vs Int32Array to read/write 32 bit values to buffers.

NAME                           REPS
-----------------------------------
Buffer                   64,553,382
Int32Array              174,148,855
const arrayBuffer = new ArrayBuffer(NUM_CASES * 4)
const buffer = Buffer.from(arrayBuffer)
const buffer32 = new Int32Array(arrayBuffer)

{
  Buffer: (index, value) => {
    buffer.writeInt32LE(value, index * 4)
    assert(buffer.readInt32LE(index * 4) === value)
  },
  Int32Array: (index, value) => {
    buffer32[index] = value
    assert(buffer32[index] === value)
  },
  DataView: () => {
    const c = cases[caseIndex]
    view.setInt32(caseIndex * 4, c, true)
    assert(view.getInt32(caseIndex * 4, true) === c)
    if (++caseIndex === NUM_CASES) {
      caseIndex = 0
    }
  },
}
@ronag ronag changed the title Buffers and 32 bit writes buffer.writeXIntYYZZ Nov 14, 2022
@ronag ronag changed the title buffer.writeXIntYYZZ buffer.(read|write)(Uint|Int)(LE|BE)(8|16|32) Nov 14, 2022
@ronag ronag changed the title buffer.(read|write)(Uint|Int)(LE|BE)(8|16|32) Buffer.(read|write)(U)Int(LE|BE)(8|16|32) Nov 14, 2022
@ronag
Copy link
Member Author

ronag commented Nov 15, 2022

Would using https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView help?

@ronag
Copy link
Member Author

ronag commented Nov 15, 2022

Buffer                   64,382,619
Int32Array              173,271,415
DataView                157,751,918

@ronag
Copy link
Member Author

ronag commented Jan 10, 2023

I was thinking maybe something like this could be faster:

class FasterBuffer extends FastBuffer {
  constructor(...args) {
    super(...args)
    this._view = new DataView(this.buffer, this.byteOffset, this.byteLength)
  }

  writeInt32LE(value, offset) {
    this._view.setInt32(offset, value, true)
  }

  readInt32LE(offset) {
    this._view.getInt32(offset, true)
  }
}

@anonrig
Copy link
Member

anonrig commented Feb 19, 2023

Deno is doing this: denoland/deno#17815

@aapoalas
Copy link

aapoalas commented Feb 19, 2023

Word of warning from the author of the linked Deno PR: The performance benefit is nothing that spectacular. I'm not sure how the Node.js Buffer method implementations are written, but the Deno std/node Buffer polyfills were written with basically byte-by-byte reading/writing into the Uint8Array that underpins the Buffer class polyfill.

When I tried out the speed of reading or writing into an ArrayBuffer through a plain DataView, it was 2-3 times faster than the Buffer polyfills. Unfortunately, when I tried to do the same using a "hidden" DataView like is proposed here with the this._view, the performance was between roughly equal to perhaps 5% better. The main benefit in Deno's case would be fewer lines of code for similar or just a tad better performance.

My guess is that V8 is somehow "gatekeeping" some optimisations behind a precondition that is not fulfilled by the "hidden" DataView. Though, my tests were done with not the original code itself but an imitation that used the class keyword instead of a plain Function so I cannot actually say for certain that the same would apply equally for Deno or Node.

EDIT:

Here's some numbers from my branch:

read int32 LE         8.86 ns/iter    (8.79 ns … 16.05 ns)   8.82 ns    9.5 ns   9.77 ns
read int32 BE         8.88 ns/iter     (8.59 ns … 15.3 ns)   8.83 ns    9.7 ns  11.35 ns
read uint32 LE        8.88 ns/iter    (8.59 ns … 15.95 ns)   8.83 ns   9.62 ns  11.15 ns
read uint32 BE        8.87 ns/iter    (8.79 ns … 15.39 ns)   8.82 ns   9.62 ns  10.15 ns
DataView int32 LE      4.1 ns/iter     (3.58 ns … 6.92 ns)   4.06 ns   5.49 ns   5.49 ns
DataView int32 BE     4.07 ns/iter     (3.58 ns … 7.25 ns)   4.06 ns   4.44 ns   4.56 ns
DataView uint32 LE    4.07 ns/iter     (3.59 ns … 6.53 ns)   4.06 ns   4.42 ns   4.58 ns
DataView uint32 BE    4.07 ns/iter     (3.58 ns … 6.05 ns)   4.06 ns   4.37 ns   4.52 ns

All of these are working with an 8 byte buffer, the 4 on the top is the Deno Buffer polyfill and bottom ones are as named the plain DataView. Both eventually go through the same DataView#get(U)Int32 APIs. Only, calling directly is twice as fast.

@aapoalas
Copy link

Update: Starting in V8 12.0 it seems that we'll see improved performance for Buffer APIs from this CL. I am not sure how this will affect the DataView vs self-built APIs performance balance, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants