Repository navigation
Buffer bounds checks slow (writeUInt8 with noAssert = false) #11245
Description
Activity
- addedbufferIssues and PRs related to the buffer subsystem.Issues and PRs related to the buffer subsystem.
on Feb 8, 2017 Pull requests are always welcome, but since brackets do not throw on an invalid index, there would have to be explicit range checking (maybe even for performance reasons?).
I wonder if anyone relies on the exceptions thrown by
checkInt. If Buffer were to be designed now, we wouldn't introduce the range checking at all since there is no undefined behavior now - assigning out-of-bounds is just no-op. Unfortunately there seems to be no deprecation path that would let us get rid of it.Reacted by Anna HenningsenI decided to run some benchmarks, some experiments and some more benchmarks on my 64-bit Windows 10 machine.
Node's own benchmarks confirm that
noAssert=falsecauses a major performance degradation:The checkInt function that's called when
noAssertisfalsehas three checks. I tried removing each one by one to see if a specific check is responsible for the slowdown. Removing the value check or the range check individually didn't make much difference. Removing them both (leaving just the Uint8Array check) didn't either:Removing the Uint8Array check (and leaving the other two), however, made a huge difference:
Without the Uint8Array check,
noAssert=falsestill causes a significant slowdown, but at least it's not tenfold anymore:I have no idea why exactly this check was necessary in the first place (it was added in one huge commit), so I propose the following course of action:
- Submit a PR removing the Uint8Array check. It shouldn't even be semver-major.
- Discuss switching
noAssertto beingtrueby default. Checks that introduce a performance degradation and are in most cases unnecessary should be opt-in.
/cc @nodejs/collaborators
Reacted by iwsfg, silverwind and Christian d'HeureuseReacted by Andreas Madsen- added a commit that references this issue
on Feb 16, 2017 # v7.7.1 $ node buftest.js Assigning buf[i], buf[i+1]: 442.359ms buf.writeUInt16LE, noAssert: 404.190ms buf.writeUInt16LE, without noAssert: 11098.851ms Assigning buf[i]: 253.650ms buf.writeUInt8, noAssert: 288.419ms buf.writeUInt8, without noAssert: 11238.774ms # master $ ./node buftest.js Assigning buf[i], buf[i+1]: 541.285ms buf.writeUInt16LE, noAssert: 416.756ms buf.writeUInt16LE, without noAssert: 1000.491ms Assigning buf[i]: 300.127ms buf.writeUInt8, noAssert: 286.881ms buf.writeUInt8, without noAssert: 830.433ms
The slow-down is not nearly as drastic. Closing.




The
writefamily of functions onBufferobjects (buf.writeUInt8etc.) have a lot of overhead whennoAssertisn'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):
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
noAssertis not enabled.Benchmark code:
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 withnoAssert = false?