Skip to content

buffer: optimize Buffer.prototype.copy#63828

Open
ronag wants to merge 2 commits into
nodejs:mainfrom
ronag:buffer-faster-copy
Open

buffer: optimize Buffer.prototype.copy#63828
ronag wants to merge 2 commits into
nodejs:mainfrom
ronag:buffer-faster-copy

Conversation

@ronag

@ronag ronag commented Jun 10, 2026

Copy link
Copy Markdown
Member

Route the native backing of Buffer.prototype.copy (CopyImpl, the _copy binding) through V8's new v8::ArrayBuffer::CopyArrayBufferBytes API instead of materializing an ArrayBufferViewContents and doing a manual memmove. This speeds up partial copies (sourceStart > 0). All copies now go through this binding: the previous %TypedArray%.prototype.set fast-path for whole-buffer copies is dropped, since it would throw on a detached or immutable target rather than report a 0-byte no-op, and the benchmarks below show the native path is comparable for that case.

V8 clamps the byte range and handles detached and immutable buffers. When both sides are backed by a SharedArrayBuffer the relaxed-atomic overload is used, which honors the SharedArrayBuffer memory model. Mixed copies (one SharedArrayBuffer, one ArrayBuffer) use the regular overload.

copy() now reports the number of bytes actually copied, as returned by V8: 0 when the target is backed by a detached or immutable ArrayBuffer. The copy is then a no-op rather than a write to read-only memory.

buffer-copy.js vs node v26.3.0 (x64, 30 runs):
partial=false bytes=1024: +7.91% ()
partial=false bytes=128: +0.17%
partial=false bytes=8: -0.89%
partial=true bytes=1024: +22.33% (
)
partial=true bytes=128: +19.58% ()
partial=true bytes=8: +18.70% (
)

The V8 API is carried as a floating patch cherry-picked from:

https://chromium-review.googlesource.com/c/v8/v8/+/7735151

That CL is authored by a Node.js collaborator (Ben Noordhuis) and is expected to land upstream, so the floating patch should be easy to maintain: it touches nothing but the public ArrayBuffer API and can be dropped wholesale on the next V8 upgrade that includes it. v8_embedder_string is bumped to -node.21 accordingly.

This supersedes the prototype in
#62491, which added a bespoke ArrayBufferView::FastCopy instead of using the upstream-friendly CopyArrayBufferBytes API.

Adds SharedArrayBuffer and immutable-ArrayBuffer coverage to the buffer copy tests.

Assisted-by: Claude Opus 4.8 (1M context) noreply@anthropic.com

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/gyp
  • @nodejs/security-wg
  • @nodejs/v8-update

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Jun 10, 2026
@ronag ronag requested review from jasnell and mcollina June 10, 2026 06:51
@ronag

ronag commented Jun 10, 2026

Copy link
Copy Markdown
Member Author

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Node’s Buffer.prototype.copy() implementation to use V8’s new ArrayBuffer::CopyArrayBufferBytes / SharedArrayBuffer::CopyArrayBufferBytes APIs, improving performance for partial copies and ensuring detached/immutable targets become no-op copies that report 0 bytes copied.

Changes:

  • Route the native _copy binding through V8’s new CopyArrayBufferBytes API (including relaxed-atomic handling when both sides are SharedArrayBuffer-backed).
  • Update JS-side copy() to return the actual number of bytes copied (as reported by the native binding), including 0 for immutable/detached targets.
  • Add test coverage for SharedArrayBuffer copies and immutable ArrayBuffer targets, plus V8 cctests and API header/docs updates.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/parallel/test-buffer-copy.js Adds SharedArrayBuffer copy coverage (including overlap + byteOffset cases).
test/parallel/test-buffer-copy-immutable.js Adds coverage for copying into immutable ArrayBuffer-backed targets (expects 0 bytes copied).
src/node_buffer.cc Reworks native copy to call V8 CopyArrayBufferBytes and return actual copied byte count.
lib/buffer.js Simplifies JS copy path to rely on native _copy return value for bytes-copied semantics.
deps/v8/test/cctest/test-api-array-buffer.cc Adds V8 cctests for the new CopyArrayBufferBytes APIs (detached + immutable cases).
deps/v8/src/api/api.cc Implements ArrayBuffer::CopyArrayBufferBytes and SharedArrayBuffer::CopyArrayBufferBytes.
deps/v8/include/v8-array-buffer.h Exposes the new public V8 API methods and documents their behavior.
common.gypi Bumps v8_embedder_string to reflect the added floating V8 patch.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/parallel/test-buffer-copy.js Outdated
Comment thread deps/v8/include/v8-array-buffer.h
Comment thread lib/buffer.js
Comment thread test/parallel/test-buffer-copy-immutable.js Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

@marco-ippolito

Copy link
Copy Markdown
Member

can you split the v8 patch commit and the implementation commit?

Add v8::ArrayBuffer::CopyArrayBufferBytes and
v8::SharedArrayBuffer::CopyArrayBufferBytes, which copy a byte range
from one backing store to another. V8 clamps the byte range and handles
detached and immutable buffers: nothing is copied when the source is
detached or the target is detached or immutable, and the number of bytes
actually copied is returned. The SharedArrayBuffer overload uses a
relaxed-atomic memmove that honors the SharedArrayBuffer memory model.

Carried as a floating patch cherry-picked from:

    https://chromium-review.googlesource.com/c/v8/v8/+/7735151

That CL is authored by a Node.js collaborator (Ben Noordhuis) and is
expected to land upstream, so the floating patch should be easy to
maintain: it touches nothing but the public ArrayBuffer API and can be
dropped wholesale on the next V8 upgrade that includes it.
v8_embedder_string is bumped to -node.21 accordingly.

Refs: https://chromium-review.googlesource.com/c/v8/v8/+/7735151
Signed-off-by: Robert Nagy <ronagy@icloud.com>
Comment thread src/node_buffer.cc Outdated
Route the native backing of Buffer.prototype.copy (CopyImpl, the `_copy`
binding) through V8's new v8::ArrayBuffer::CopyArrayBufferBytes API
(added in the preceding commit) instead of materializing an
ArrayBufferViewContents and doing a manual memmove. This speeds up
partial copies (sourceStart > 0). All copies now go through this
binding: the previous %TypedArray%.prototype.set fast-path for
whole-buffer copies is dropped, since it would throw on a detached or
immutable target rather than report a 0-byte no-op, and the benchmarks
below show the native path is comparable for that case.

When both sides are backed by a SharedArrayBuffer the relaxed-atomic
overload is used, which honors the SharedArrayBuffer memory model.
Mixed copies (one SharedArrayBuffer, one ArrayBuffer) use the regular
overload.

copy() now reports the number of bytes actually copied, as returned by
V8: 0 when the target is backed by a detached or immutable ArrayBuffer.
The copy is then a no-op rather than a write to read-only memory.

The native binding now plumbs byte offsets and the copied-byte count
through as size_t, passed across the fast/slow API boundary as doubles
(exact for integer values below 2^53), so copies that cross the 4 GiB
boundary are no longer truncated to 32 bits.

buffer-copy.js vs node v26.3.0 (x64, 30 runs):
  partial=false bytes=1024:  +7.91%  (***)
  partial=false bytes=128:   +0.17%
  partial=false bytes=8:     -0.89%
  partial=true  bytes=1024: +22.33%  (***)
  partial=true  bytes=128:  +19.58%  (***)
  partial=true  bytes=8:    +18.70%  (***)

This supersedes the prototype in
nodejs#62491, which added a bespoke
ArrayBufferView::FastCopy instead of using the upstream-friendly
CopyArrayBufferBytes API.

Adds SharedArrayBuffer and immutable-ArrayBuffer coverage to the buffer
copy tests, plus a pummel regression test for copies larger than 2^32
bytes.

Refs: nodejs#55422
Signed-off-by: Robert Nagy <ronagy@icloud.com>
Assisted-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bnoordhuis

Copy link
Copy Markdown
Member

That CL is authored by a Node.js collaborator (Ben Noordhuis) and is expected to land upstream

For posterity, said CL was merged upstream earlier today.

@ronag ronag added request-ci Add this label to start a Jenkins CI on a PR. author ready PRs that have at least one approval, no pending requests for changes, and a CI started. labels Jun 10, 2026
@ronag ronag requested a review from Renegade334 June 11, 2026 18:33
Comment thread lib/buffer.js
Comment on lines 271 to 280
@@ -279,13 +279,10 @@ function _copyActual(source, target, targetStart, sourceStart, sourceEnd, isUint
if (nb <= 0)
return 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Am I right in saying that CopyArrayBufferBytesImpl does its own clamping? If so, could we get rid of this wrapper entirely?

@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Jun 11, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@ronag ronag added the commit-queue Add this label to land a pull request using GitHub Actions. label Jun 12, 2026
@nodejs-github-bot nodejs-github-bot added commit-queue-failed An error occurred while landing this pull request using GitHub Actions. and removed commit-queue Add this label to land a pull request using GitHub Actions. labels Jun 12, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator
Commit Queue failed
- Loading data for nodejs/node/pull/63828
✔  Done loading data for nodejs/node/pull/63828
----------------------------------- PR info ------------------------------------
Title      buffer: optimize Buffer.prototype.copy (#63828)
Author     Robert Nagy <ronagy@icloud.com> (@ronag)
Branch     ronag:buffer-faster-copy -> nodejs:main
Labels     lib / src, author ready, needs-ci
Commits    2
 - deps: V8: add CopyArrayBufferBytes API
 - buffer: optimize Buffer.prototype.copy
Committers 1
 - Robert Nagy <ronagy@icloud.com>
PR-URL: https://github.com/nodejs/node/pull/63828
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
------------------------------ Generated metadata ------------------------------
PR-URL: https://github.com/nodejs/node/pull/63828
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
--------------------------------------------------------------------------------
   ℹ  This PR was created on Wed, 10 Jun 2026 06:51:37 GMT
   ✔  Approvals: 2
   ✔  - Yagiz Nizipli (@anonrig) (TSC): https://github.com/nodejs/node/pull/63828#pullrequestreview-4467706416
   ✔  - René (@Renegade334): https://github.com/nodejs/node/pull/63828#pullrequestreview-4479569028
   ✘  GitHub CI failed with status: FAILURE
   ℹ  Last Benchmark CI on 2026-06-10T07:02:49Z: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/1868/
   ℹ  Last Full PR CI on 2026-06-11T18:53:12Z: https://ci.nodejs.org/job/node-test-pull-request/74062/
- Querying data for job/node-test-pull-request/74062/
✔  Build data downloaded
- Querying failures of job/node-test-commit/88614/
✔  Data downloaded
   ✘  1 failure(s) on the last Jenkins CI run
--------------------------------------------------------------------------------
   ✔  Aborted `git node land` session in /home/runner/work/node/node/.ncu
https://github.com/nodejs/node/actions/runs/27400061810

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@ronag ronag added the commit-queue Add this label to land a pull request using GitHub Actions. label Jun 12, 2026
@nodejs-github-bot nodejs-github-bot removed the commit-queue Add this label to land a pull request using GitHub Actions. label Jun 12, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator
Commit Queue failed
- Loading data for nodejs/node/pull/63828
✔  Done loading data for nodejs/node/pull/63828
----------------------------------- PR info ------------------------------------
Title      buffer: optimize Buffer.prototype.copy (#63828)
Author     Robert Nagy <ronagy@icloud.com> (@ronag)
Branch     ronag:buffer-faster-copy -> nodejs:main
Labels     lib / src, author ready, needs-ci, commit-queue-failed
Commits    2
 - deps: V8: add CopyArrayBufferBytes API
 - buffer: optimize Buffer.prototype.copy
Committers 1
 - Robert Nagy <ronagy@icloud.com>
PR-URL: https://github.com/nodejs/node/pull/63828
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
------------------------------ Generated metadata ------------------------------
PR-URL: https://github.com/nodejs/node/pull/63828
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
--------------------------------------------------------------------------------
   ℹ  This PR was created on Wed, 10 Jun 2026 06:51:37 GMT
   ✔  Approvals: 2
   ✔  - Yagiz Nizipli (@anonrig) (TSC): https://github.com/nodejs/node/pull/63828#pullrequestreview-4467706416
   ✔  - René (@Renegade334): https://github.com/nodejs/node/pull/63828#pullrequestreview-4479569028
   ✔  Last GitHub CI successful
   ℹ  Last Benchmark CI on 2026-06-12T06:59:29Z: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/1868/
   ℹ  Last Full PR CI on 2026-06-12T07:04:01Z: https://ci.nodejs.org/job/node-test-pull-request/74069/
- Querying data for job/node-test-pull-request/74069/
✔  Build data downloaded
   ✔  Last Jenkins CI successful
--------------------------------------------------------------------------------
   ✔  No git cherry-pick in progress
   ✔  No git am in progress
   ✔  No git rebase in progress
--------------------------------------------------------------------------------
- Bringing origin/main up to date...
From https://github.com/nodejs/node
 * branch                  main       -> FETCH_HEAD
✔  origin/main is now up-to-date
- Downloading patch for 63828
From https://github.com/nodejs/node
 * branch                  refs/pull/63828/merge -> FETCH_HEAD
✔  Fetched commits as 144f79595670..da189fb122ca
--------------------------------------------------------------------------------
[main b17b65061a] deps: V8: add CopyArrayBufferBytes API
 Author: Robert Nagy <ronagy@icloud.com>
 Date: Wed Jun 10 11:09:44 2026 +0200
 4 files changed, 125 insertions(+), 1 deletion(-)
[main 00629a26db] buffer: optimize Buffer.prototype.copy
 Author: Robert Nagy <ronagy@icloud.com>
 Date: Wed Jun 10 11:09:48 2026 +0200
 5 files changed, 205 insertions(+), 35 deletions(-)
 create mode 100644 test/parallel/test-buffer-copy-immutable.js
 create mode 100644 test/pummel/test-buffer-large-size-copy.js
   ✔  Patches applied
There are 2 commits in the PR. Attempting autorebase.
(node:358) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.
(Use `node --trace-deprecation ...` to show where the warning was created)
Rebasing (2/4)
Executing: git node land --amend --yes
--------------------------------- New Message ----------------------------------
deps: V8: add CopyArrayBufferBytes API

Add v8::ArrayBuffer::CopyArrayBufferBytes and
v8::SharedArrayBuffer::CopyArrayBufferBytes, which copy a byte range
from one backing store to another. V8 clamps the byte range and handles
detached and immutable buffers: nothing is copied when the source is
detached or the target is detached or immutable, and the number of bytes
actually copied is returned. The SharedArrayBuffer overload uses a
relaxed-atomic memmove that honors the SharedArrayBuffer memory model.

Carried as a floating patch cherry-picked from:

https://chromium-review.googlesource.com/c/v8/v8/+/7735151

That CL is authored by a Node.js collaborator (Ben Noordhuis) and is
expected to land upstream, so the floating patch should be easy to
maintain: it touches nothing but the public ArrayBuffer API and can be
dropped wholesale on the next V8 upgrade that includes it.
v8_embedder_string is bumped to -node.21 accordingly.

Refs: https://chromium-review.googlesource.com/c/v8/v8/+/7735151
Signed-off-by: Robert Nagy <ronagy@icloud.com>
PR-URL: #63828
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>

[detached HEAD 1a6c69bcce] deps: V8: add CopyArrayBufferBytes API
Author: Robert Nagy <ronagy@icloud.com>
Date: Wed Jun 10 11:09:44 2026 +0200
4 files changed, 125 insertions(+), 1 deletion(-)
Rebasing (3/4)
Rebasing (4/4)
Executing: git node land --amend --yes
--------------------------------- New Message ----------------------------------
buffer: optimize Buffer.prototype.copy

Route the native backing of Buffer.prototype.copy (CopyImpl, the _copy
binding) through V8's new v8::ArrayBuffer::CopyArrayBufferBytes API
(added in the preceding commit) instead of materializing an
ArrayBufferViewContents and doing a manual memmove. This speeds up
partial copies (sourceStart > 0). All copies now go through this
binding: the previous %TypedArray%.prototype.set fast-path for
whole-buffer copies is dropped, since it would throw on a detached or
immutable target rather than report a 0-byte no-op, and the benchmarks
below show the native path is comparable for that case.

When both sides are backed by a SharedArrayBuffer the relaxed-atomic
overload is used, which honors the SharedArrayBuffer memory model.
Mixed copies (one SharedArrayBuffer, one ArrayBuffer) use the regular
overload.

copy() now reports the number of bytes actually copied, as returned by
V8: 0 when the target is backed by a detached or immutable ArrayBuffer.
The copy is then a no-op rather than a write to read-only memory.

The native binding now plumbs byte offsets and the copied-byte count
through as size_t, passed across the fast/slow API boundary as doubles
(exact for integer values below 2^53), so copies that cross the 4 GiB
boundary are no longer truncated to 32 bits.

buffer-copy.js vs node v26.3.0 (x64, 30 runs):
partial=false bytes=1024: +7.91% ()
partial=false bytes=128: +0.17%
partial=false bytes=8: -0.89%
partial=true bytes=1024: +22.33% (
)
partial=true bytes=128: +19.58% ()
partial=true bytes=8: +18.70% (
)

This supersedes the prototype in
#62491, which added a bespoke
ArrayBufferView::FastCopy instead of using the upstream-friendly
CopyArrayBufferBytes API.

Adds SharedArrayBuffer and immutable-ArrayBuffer coverage to the buffer
copy tests, plus a pummel regression test for copies larger than 2^32
bytes.

Refs: #55422
Signed-off-by: Robert Nagy <ronagy@icloud.com>
Assisted-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR-URL: #63828
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>

[detached HEAD 745869c1e5] buffer: optimize Buffer.prototype.copy
Author: Robert Nagy <ronagy@icloud.com>
Date: Wed Jun 10 11:09:48 2026 +0200
5 files changed, 205 insertions(+), 35 deletions(-)
create mode 100644 test/parallel/test-buffer-copy-immutable.js
create mode 100644 test/pummel/test-buffer-large-size-copy.js
Successfully rebased and updated refs/heads/main.

ℹ Add commit-queue-squash label to land the PR as one commit, or commit-queue-rebase to land as separate commits.

https://github.com/nodejs/node/actions/runs/27416075610

@ronag ronag added commit-queue-rebase Add this label to allow the Commit Queue to land a PR in several commits. and removed commit-queue-failed An error occurred while landing this pull request using GitHub Actions. labels Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author ready PRs that have at least one approval, no pending requests for changes, and a CI started. commit-queue-rebase Add this label to allow the Commit Queue to land a PR in several commits. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants