buffer: support aligned allocations - #2
Open
ronag wants to merge 1 commit into
Open
Conversation
ronag
force-pushed
the
buffer-aligned-alloc
branch
3 times, most recently
from
August 4, 2026 14:56
bb15ba4 to
f35f20d
Compare
Add an optional `alignment` argument to `Buffer.allocUnsafe()` and
`Buffer.allocUnsafeSlow()`, which guarantees that the memory backing the
returned buffer starts at an address that is a multiple of `alignment`.
Some operating system interfaces refuse to work with unaligned memory.
The motivating case is unbuffered ("direct") file I/O: a read or write
on a descriptor opened with `O_DIRECT` fails with `EINVAL` unless the
buffer address is a multiple of the logical block size of the underlying
device. Until now there was no way to obtain such a buffer from JS,
since the address of a backing store can neither be observed nor chosen.
Alignment is also worth having purely for performance, for instance to
keep a hot buffer from straddling one more cache line than its size
requires.
V8 does not allow picking the address of a backing store, so alignment
is instead achieved by over-allocating `alignment - 1` bytes and
positioning the buffer at the first suitably aligned byte within them.
The new `arrayBufferAlignedOffset()` binding computes that offset.
Addresses are not stable across snapshot serialization, so the binding
reports no padding while a snapshot is being built. Otherwise the
snapshot would capture where this particular process happened to
allocate and stop being reproducible. Buffers restored from a snapshot
are consequently not aligned; the pool works around this by recreating
itself in a deserialize callback.
The `Buffer.allocUnsafe()` pool is now aligned to a cache line itself,
which lets pooled allocations satisfy any alignment up to 64 bytes by
padding their offset into the pool rather than allocating separately.
Assisted-by: Claude/Opus 5
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Robert Nagy <ronagy@icloud.com>
ronag
force-pushed
the
buffer-aligned-alloc
branch
from
August 4, 2026 15:57
f35f20d to
542b4f1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an optional
alignmentargument toBuffer.allocUnsafe()andBuffer.allocUnsafeSlow(), guaranteeing that the memory backing the returnedbuffer starts at an address that is a multiple of
alignment.Why
Some OS interfaces refuse to work with unaligned memory. The motivating case is
unbuffered ("direct") file I/O: a read or write on a descriptor opened with
O_DIRECTfails withEINVALunless the buffer address is a multiple of thedevice's logical block size. There was previously no way to get such a buffer
from JS — the address of a backing store can neither be observed nor chosen.
Alignment is also useful purely for performance, e.g. keeping a hot buffer from
straddling one more cache line than its size requires.
How
V8 does not allow picking the address of a backing store, so alignment is
achieved by over-allocating
alignment - 1bytes and positioning the buffer atthe first suitably aligned byte within them. A new
arrayBufferAlignedOffset()binding computes that offset.
It returns an offset rather than a finished
Bufferfor two reasons: externalbacking stores are rejected outright when the V8 sandbox is enabled, and
Buffer::New()is not usable duringbuffer.jsevaluation — the bufferprototype is only wired up after
require('buffer')returns, which the poolcreation runs before.
The
Buffer.allocUnsafe()pool is now cache-line aligned itself, so pooledallocations can satisfy any alignment up to 64 bytes by padding their offset
into the pool instead of allocating separately.
poolOffsetis therefore nowtracked relative to a new
poolBase.Status
lint-mdreports only the expectedpr-url: REPLACEMEwarnings.changes:entries still need the real PR number.