Skip to content

Buffer bounds checks slow (writeUInt8 with noAssert = false) #11245

Closed
@joliss

Description

The write family of functions on Buffer objects (buf.writeUInt8 etc.) have a lot of overhead when noAssert isn't set to true -- much more than I would normally expect from a bounds check.

Here are some timings I got for filling a 100MB buffer (Node 7.5.0 on 64-bit Linux):

Assigning buf[i], buf[i+1]: 392.496ms
buf.writeUInt16LE, noAssert: 342.778ms
buf.writeUInt16LE, without noAssert: 9072.283ms

Assigning buf[i]: 288.415ms
buf.writeUInt8, noAssert: 312.455ms
buf.writeUInt8, without noAssert: 8725.956ms

I had a lot of timing variation, so I don't think the indexed-vs-writeUInt8 differences are meaningful. This might be because we're down to about 10 CPU cycles per iteration. My bug report is about the unexpected order-of-magnitude difference when noAssert is not enabled.

Benchmark code:

let count = 100000000
let buf1, buf2

function timeAssign16() {
  let buf1
  buf1 = Buffer.allocUnsafe(count * 2)
  console.time('Assigning buf[i], buf[i+1]')
  for (let i = 0; i < count; i++) {
    let uint16 = i % 2**16
    buf1[i*2] = uint16 % 256
    buf1[i*2 + 1] = (uint16 >> 8) % 256
  }
  console.timeEnd('Assigning buf[i], buf[i+1]')
  return buf1
}

function timeWrite16() {
  let buf2
  buf2 = Buffer.allocUnsafe(count * 2)
  console.time('buf.writeUInt16LE, noAssert')
  for (let i = 0; i < count; i++) {
    buf2.writeUInt16LE(i % 2**16, i * 2, true)
  }
  console.timeEnd('buf.writeUInt16LE, noAssert')
  return buf2
}

function timeWrite16Checked() {
  let buf2
  buf2 = Buffer.allocUnsafe(count * 2)
  console.time('buf.writeUInt16LE, without noAssert')
  for (let i = 0; i < count; i++) {
    buf2.writeUInt16LE(i % 2**16, i * 2)
  }
  console.timeEnd('buf.writeUInt16LE, without noAssert')
  return buf2
}

buf1 = timeAssign16()
buf2 = timeWrite16()
buf2 = timeWrite16Checked()
if (buf1.compare(buf2) !== 0) throw 'error'

console.log()

function timeAssign8() {
  let buf1
  buf1 = Buffer.allocUnsafe(count)
  console.time('Assigning buf[i]')
  for (let i = 0; i < count; i++) {
    buf1[i] = i % 256
  }
  console.timeEnd('Assigning buf[i]')
  return buf1
}

function timeWrite8() {
  let buf2
  buf2 = Buffer.allocUnsafe(count)
  console.time('buf.writeUInt8, noAssert')
  for (let i = 0; i < count; i++) {
    buf2.writeUInt8(i % 256, i, true)
  }
  console.timeEnd('buf.writeUInt8, noAssert')
  return buf2
}

function timeWrite8Checked() {
  let buf2
  buf2 = Buffer.allocUnsafe(count)
  console.time('buf.writeUInt8, without noAssert')
  for (let i = 0; i < count; i++) {
    buf2.writeUInt8(i % 256, i)
  }
  console.timeEnd('buf.writeUInt8, without noAssert')
  return buf2
}

buf1 = timeAssign8()
buf2 = timeWrite8()
buf2 = timeWrite8Checked()
if (buf1.compare(buf2) !== 0) throw 'error'

Related: #11244 ("Unclear if Buffer buf[i] is bounds-checked"). If buf[i] is indeed bounds-checked, then surely we should be able to achieve the same performance with noAssert = false?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    bufferIssues and PRs related to the buffer subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions