From 785a5642829ed95dcd28646091b40d199c4329f4 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Sun, 29 Mar 2026 10:18:18 +0200 Subject: [PATCH 1/4] buffer: optimize Buffer.copy This removes some of the overhead by extending the V8 api. PR-URL: https://github.com/nodejs/node/pull/62491 --- deps/v8/include/v8-array-buffer.h | 11 +++++++++++ deps/v8/src/api/api.cc | 33 +++++++++++++++++++++++++++++++ doc/api/buffer.md | 12 ++++++++--- lib/buffer.js | 8 ++------ src/node_buffer.cc | 29 +++++++++++---------------- 5 files changed, 66 insertions(+), 27 deletions(-) diff --git a/deps/v8/include/v8-array-buffer.h b/deps/v8/include/v8-array-buffer.h index 3e64ece5debda3..aa14340bd25123 100644 --- a/deps/v8/include/v8-array-buffer.h +++ b/deps/v8/include/v8-array-buffer.h @@ -456,6 +456,17 @@ class V8_EXPORT ArrayBufferView : public Object { */ bool HasBuffer() const; + /** + * Copies |count| bytes from |source| starting at |source_start| into + * |target| starting at |target_start| using memmove semantics. Unlike + * CopyContents, this method handles both source and destination, supports + * byte offsets, and requires no HandleScope. Behaviour is undefined + * if either view is detached or if the range exceeds the view bounds. + */ + static void FastCopy(const ArrayBufferView* source, size_t source_start, + ArrayBufferView* target, size_t target_start, + size_t count); + V8_INLINE static ArrayBufferView* Cast(Value* value) { #ifdef V8_ENABLE_CHECKS CheckCast(value); diff --git a/deps/v8/src/api/api.cc b/deps/v8/src/api/api.cc index 5a879e9ff5d9e8..ad84aec659f00a 100644 --- a/deps/v8/src/api/api.cc +++ b/deps/v8/src/api/api.cc @@ -9044,6 +9044,39 @@ size_t v8::ArrayBufferView::CopyContents(void* dest, size_t byte_length) { return bytes_to_copy; } +// static +void v8::ArrayBufferView::FastCopy(const ArrayBufferView* source, + size_t source_start, + ArrayBufferView* target, + size_t target_start, size_t count) { + i::DisallowGarbageCollection no_gc; + auto src = Utils::OpenDirectHandle(source); + auto dst = Utils::OpenDirectHandle(target); + + if (V8_UNLIKELY(src->IsDetachedOrOutOfBounds() || + dst->IsDetachedOrOutOfBounds())) { + return; + } + + char* src_data; + if (V8_LIKELY(i::IsJSTypedArray(*src))) { + src_data = reinterpret_cast(i::Cast(*src)->DataPtr()); + } else { + src_data = reinterpret_cast( + i::Cast(*src)->data_pointer()); + } + + char* dst_data; + if (V8_LIKELY(i::IsJSTypedArray(*dst))) { + dst_data = reinterpret_cast(i::Cast(*dst)->DataPtr()); + } else { + dst_data = reinterpret_cast( + i::Cast(*dst)->data_pointer()); + } + + memmove(dst_data + target_start, src_data + source_start, count); +} + v8::MemorySpan v8::ArrayBufferView::GetContents( v8::MemorySpan storage) { internal::DisallowGarbageCollection no_gc; diff --git a/doc/api/buffer.md b/doc/api/buffer.md index 329d1f3f9915df..6703154885e185 100644 --- a/doc/api/buffer.md +++ b/doc/api/buffer.md @@ -1767,9 +1767,14 @@ added: v0.1.90 Copies data from a region of `buf` to a region in `target`, even if the `target` memory region overlaps with `buf`. -[`TypedArray.prototype.set()`][] performs the same operation, and is available -for all TypedArrays, including Node.js `Buffer`s, although it takes -different function arguments. +[`TypedArray.prototype.set()`][] performs a similar operation and is available +for all TypedArrays, including Node.js `Buffer`s, although it takes different +function arguments. It differs from `buf.copy()` in two ways: + +1. If either buffer is detached, it throws a `TypeError` instead of performing + a no-op. +2. If either buffer is backed by a [`SharedArrayBuffer`][], it performs a + slower copy based on a stricter memory model. ```mjs import { Buffer } from 'node:buffer'; @@ -5588,6 +5593,7 @@ introducing security vulnerabilities into an application. [`ERR_OUT_OF_RANGE`]: errors.md#err_out_of_range [`JSON.stringify()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify [`Number.MAX_SAFE_INTEGER`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER +[`SharedArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer [`String.prototype.indexOf()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf [`String.prototype.lastIndexOf()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf [`String.prototype.length`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length diff --git a/lib/buffer.js b/lib/buffer.js index 4d96a536a15f8b..e29d8c3e7d8bbd 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -267,7 +267,7 @@ function copyImpl(source, target, targetStart, sourceStart, sourceEnd) { return _copyActual(source, target, targetStart, sourceStart, sourceEnd); } -function _copyActual(source, target, targetStart, sourceStart, sourceEnd, isUint8Copy = false) { +function _copyActual(source, target, targetStart, sourceStart, sourceEnd) { if (sourceEnd - sourceStart > target.byteLength - targetStart) sourceEnd = sourceStart + target.byteLength - targetStart; @@ -279,11 +279,7 @@ function _copyActual(source, target, targetStart, sourceStart, sourceEnd, isUint if (nb <= 0) return 0; - if (sourceStart === 0 && nb === sourceLen && (isUint8Copy || isUint8Array(target))) { - TypedArrayPrototypeSet(target, source, targetStart); - } else { - _copy(source, target, targetStart, sourceStart, nb); - } + _copy(source, target, targetStart, sourceStart, nb); return nb; } diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 362ac268483ea3..e1c8b840c64e39 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -585,26 +585,17 @@ void StringSlice(const FunctionCallbackInfo& args) { } } -void CopyImpl(Local source_obj, - Local target_obj, - const uint32_t target_start, - const uint32_t source_start, - const uint32_t to_copy) { - ArrayBufferViewContents source(source_obj); - SPREAD_BUFFER_ARG(target_obj, target); - - memmove(target_data + target_start, source.data() + source_start, to_copy); -} - // Assume caller has properly validated args. void SlowCopy(const FunctionCallbackInfo& args) { - Local source_obj = args[0]; - Local target_obj = args[1]; const uint32_t target_start = args[2].As()->Value(); const uint32_t source_start = args[3].As()->Value(); const uint32_t to_copy = args[4].As()->Value(); - CopyImpl(source_obj, target_obj, target_start, source_start, to_copy); + ArrayBufferView::FastCopy(ArrayBufferView::Cast(*args[0]), + source_start, + ArrayBufferView::Cast(*args[1]), + target_start, + to_copy); args.GetReturnValue().Set(to_copy); } @@ -618,10 +609,12 @@ uint32_t FastCopy(Local receiver, uint32_t to_copy, // NOLINTNEXTLINE(runtime/references) FastApiCallbackOptions& options) { - HandleScope scope(options.isolate); - - CopyImpl(source_obj, target_obj, target_start, source_start, to_copy); - + TRACK_V8_FAST_API_CALL("buffer.copy"); + ArrayBufferView::FastCopy(ArrayBufferView::Cast(*source_obj), + source_start, + ArrayBufferView::Cast(*target_obj), + target_start, + to_copy); return to_copy; } From 415afe1b06c80296210d1113036b84b6834e2763 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Sun, 29 Mar 2026 11:17:16 +0200 Subject: [PATCH 2/4] Update v8-array-buffer.h --- deps/v8/include/v8-array-buffer.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deps/v8/include/v8-array-buffer.h b/deps/v8/include/v8-array-buffer.h index aa14340bd25123..3a31c388a585bf 100644 --- a/deps/v8/include/v8-array-buffer.h +++ b/deps/v8/include/v8-array-buffer.h @@ -460,8 +460,7 @@ class V8_EXPORT ArrayBufferView : public Object { * Copies |count| bytes from |source| starting at |source_start| into * |target| starting at |target_start| using memmove semantics. Unlike * CopyContents, this method handles both source and destination, supports - * byte offsets, and requires no HandleScope. Behaviour is undefined - * if either view is detached or if the range exceeds the view bounds. + * byte offsets, and requires no HandleScope. */ static void FastCopy(const ArrayBufferView* source, size_t source_start, ArrayBufferView* target, size_t target_start, From 68bf4825fa2bbbd1104f2290043dc67388e40f1e Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Sun, 29 Mar 2026 12:26:42 +0200 Subject: [PATCH 3/4] fixup --- benchmark/buffers/buffer-copy.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/benchmark/buffers/buffer-copy.js b/benchmark/buffers/buffer-copy.js index c2dafc8515c4f2..9caa7712036529 100644 --- a/benchmark/buffers/buffer-copy.js +++ b/benchmark/buffers/buffer-copy.js @@ -4,12 +4,13 @@ const common = require('../common.js'); const bench = common.createBenchmark(main, { bytes: [8, 128, 1024], partial: ['true', 'false'], + shared: ['true', 'false'], n: [6e6], }); -function main({ n, bytes, partial }) { - const source = Buffer.allocUnsafe(bytes); - const target = Buffer.allocUnsafe(bytes); +function main({ n, bytes, partial, shared }) { + const source = shared === 'true' ? Buffer.from(new SharedArrayBuffer(bytes)) : Buffer.allocUnsafe(bytes); + const target = shared === 'true' ? Buffer.from(new SharedArrayBuffer(bytes)) : Buffer.allocUnsafe(bytes); const sourceStart = (partial === 'true' ? Math.floor(bytes / 2) : 0); bench.start(); for (let i = 0; i < n; i++) { From 7da38b1d7d2e1a4e36f34872550fa1d4672a0ec1 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Sun, 29 Mar 2026 12:39:42 +0200 Subject: [PATCH 4/4] fixup --- deps/v8/include/v8-array-buffer.h | 4 ++-- deps/v8/src/api/api.cc | 8 ++++---- src/node_buffer.cc | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/deps/v8/include/v8-array-buffer.h b/deps/v8/include/v8-array-buffer.h index 3a31c388a585bf..19193d3762fe4c 100644 --- a/deps/v8/include/v8-array-buffer.h +++ b/deps/v8/include/v8-array-buffer.h @@ -462,8 +462,8 @@ class V8_EXPORT ArrayBufferView : public Object { * CopyContents, this method handles both source and destination, supports * byte offsets, and requires no HandleScope. */ - static void FastCopy(const ArrayBufferView* source, size_t source_start, - ArrayBufferView* target, size_t target_start, + static void FastCopy(Local source, size_t source_start, + Local target, size_t target_start, size_t count); V8_INLINE static ArrayBufferView* Cast(Value* value) { diff --git a/deps/v8/src/api/api.cc b/deps/v8/src/api/api.cc index ad84aec659f00a..81a3f91d9656ca 100644 --- a/deps/v8/src/api/api.cc +++ b/deps/v8/src/api/api.cc @@ -9045,13 +9045,13 @@ size_t v8::ArrayBufferView::CopyContents(void* dest, size_t byte_length) { } // static -void v8::ArrayBufferView::FastCopy(const ArrayBufferView* source, +void v8::ArrayBufferView::FastCopy(Local source, size_t source_start, - ArrayBufferView* target, + Local target, size_t target_start, size_t count) { i::DisallowGarbageCollection no_gc; - auto src = Utils::OpenDirectHandle(source); - auto dst = Utils::OpenDirectHandle(target); + auto src = Utils::OpenDirectHandle(*source); + auto dst = Utils::OpenDirectHandle(*target); if (V8_UNLIKELY(src->IsDetachedOrOutOfBounds() || dst->IsDetachedOrOutOfBounds())) { diff --git a/src/node_buffer.cc b/src/node_buffer.cc index e1c8b840c64e39..7fbfd178b354d8 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -591,9 +591,9 @@ void SlowCopy(const FunctionCallbackInfo& args) { const uint32_t source_start = args[3].As()->Value(); const uint32_t to_copy = args[4].As()->Value(); - ArrayBufferView::FastCopy(ArrayBufferView::Cast(*args[0]), + ArrayBufferView::FastCopy(args[0].As(), source_start, - ArrayBufferView::Cast(*args[1]), + args[1].As(), target_start, to_copy); @@ -610,9 +610,9 @@ uint32_t FastCopy(Local receiver, // NOLINTNEXTLINE(runtime/references) FastApiCallbackOptions& options) { TRACK_V8_FAST_API_CALL("buffer.copy"); - ArrayBufferView::FastCopy(ArrayBufferView::Cast(*source_obj), + ArrayBufferView::FastCopy(source_obj.As(), source_start, - ArrayBufferView::Cast(*target_obj), + target_obj.As(), target_start, to_copy); return to_copy;