Skip to content

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

@joliss

Description

@joliss

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    bufferIssues and PRs related to the buffer subsystem.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions