node/test/pummel/test-buffer-large-size-copy.js
Robert Nagy 4383f67279 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
https://github.com/nodejs/node/pull/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: https://github.com/nodejs/node/issues/55422
Signed-off-by: Robert Nagy <ronagy@icloud.com>
Assisted-by: Claude Opus 4.8 (1M context) <noreply@anthropic.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>
2026-06-12 23:26:19 +00:00

40 lines
1.2 KiB
JavaScript

'use strict';
const common = require('../common');
// Buffer.prototype.copy with offsets and a byte count > 2 ** 32. Regression
// test for the .copy() side of https://github.com/nodejs/node/issues/55422,
// where the byte count and the return value were truncated to 32 bits.
common.skipIf32Bits();
const assert = require('node:assert');
// A little past the 2 ** 32 boundary, with headroom for the shift below.
const size = 2 ** 32 + 16;
let buf;
try {
buf = Buffer.alloc(size);
} catch (e) {
if (
e.code === 'ERR_MEMORY_ALLOCATION_FAILED' ||
/Array buffer allocation failed/.test(e.message)
) {
common.skip('insufficient memory for Buffer.alloc');
}
throw e;
}
// Place a marker near the end of the source range, at an index > 2 ** 32.
const marker = 0x42;
buf[size - 9] = marker;
// Shift the whole buffer right by 8 bytes within itself. `to_copy` is
// size - 8 (> 2 ** 32), so a truncated count would copy only 8 bytes. The
// source byte at size - 9 must land at size - 1.
const copied = buf.copy(buf, 8, 0, size - 8);
// The return value must not be truncated to 32 bits ...
assert.strictEqual(copied, size - 8);
// ... and the byte must actually have moved across the 2 ** 32 boundary.
assert.strictEqual(buf[size - 1], marker);