Skip to content

Commit 9e36c00

Browse files
committed
buffer: optimize Buffer.copy
This removes some of the overhead by extending the V8 api. PR-URL: #62491
1 parent cc96741 commit 9e36c00

File tree

5 files changed

+62
-27
lines changed

5 files changed

+62
-27
lines changed

deps/v8/include/v8-array-buffer.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,17 @@ class V8_EXPORT ArrayBufferView : public Object {
456456
*/
457457
bool HasBuffer() const;
458458

459+
/**
460+
* Copies |count| bytes from |source| starting at |source_start| into
461+
* |target| starting at |target_start| using memmove semantics. Unlike
462+
* CopyContents, this method handles both source and destination, supports
463+
* byte offsets, and requires no HandleScope. Behaviour is undefined
464+
* if either view is detached or if the range exceeds the view bounds.
465+
*/
466+
static void FastCopy(const ArrayBufferView* source, size_t source_start,
467+
ArrayBufferView* target, size_t target_start,
468+
size_t count);
469+
459470
V8_INLINE static ArrayBufferView* Cast(Value* value) {
460471
#ifdef V8_ENABLE_CHECKS
461472
CheckCast(value);

deps/v8/src/api/api.cc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9044,6 +9044,39 @@ size_t v8::ArrayBufferView::CopyContents(void* dest, size_t byte_length) {
90449044
return bytes_to_copy;
90459045
}
90469046

9047+
// static
9048+
void v8::ArrayBufferView::FastCopy(const ArrayBufferView* source,
9049+
size_t source_start,
9050+
ArrayBufferView* target,
9051+
size_t target_start, size_t count) {
9052+
i::DisallowGarbageCollection no_gc;
9053+
auto src = Utils::OpenDirectHandle(source);
9054+
auto dst = Utils::OpenDirectHandle(target);
9055+
9056+
if (V8_UNLIKELY(src->IsDetachedOrOutOfBounds() ||
9057+
dst->IsDetachedOrOutOfBounds())) {
9058+
return;
9059+
}
9060+
9061+
char* src_data;
9062+
if (i::IsJSTypedArray(*src)) {
9063+
src_data = reinterpret_cast<char*>(i::Cast<i::JSTypedArray>(*src)->DataPtr());
9064+
} else {
9065+
src_data = reinterpret_cast<char*>(
9066+
i::Cast<i::JSDataViewOrRabGsabDataView>(*src)->data_pointer());
9067+
}
9068+
9069+
char* dst_data;
9070+
if (i::IsJSTypedArray(*dst)) {
9071+
dst_data = reinterpret_cast<char*>(i::Cast<i::JSTypedArray>(*dst)->DataPtr());
9072+
} else {
9073+
dst_data = reinterpret_cast<char*>(
9074+
i::Cast<i::JSDataViewOrRabGsabDataView>(*dst)->data_pointer());
9075+
}
9076+
9077+
memmove(dst_data + target_start, src_data + source_start, count);
9078+
}
9079+
90479080
v8::MemorySpan<uint8_t> v8::ArrayBufferView::GetContents(
90489081
v8::MemorySpan<uint8_t> storage) {
90499082
internal::DisallowGarbageCollection no_gc;

doc/api/buffer.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,9 +1767,15 @@ added: v0.1.90
17671767
Copies data from a region of `buf` to a region in `target`, even if the `target`
17681768
memory region overlaps with `buf`.
17691769

1770-
[`TypedArray.prototype.set()`][] performs the same operation, and is available
1771-
for all TypedArrays, including Node.js `Buffer`s, although it takes
1772-
different function arguments.
1770+
[`TypedArray.prototype.set()`][] performs a similar operation and is available
1771+
for all TypedArrays, including Node.js `Buffer`s, although it takes different
1772+
function arguments. It differs from `buf.copy()` in two ways:
1773+
1774+
1. If either buffer is detached, it throws a `TypeError` instead of performing
1775+
a no-op.
1776+
2. If either buffer is backed by a [`SharedArrayBuffer`][], it performs a
1777+
slower copy based on a stricter memory model.
1778+
17731779

17741780
```mjs
17751781
import { Buffer } from 'node:buffer';

lib/buffer.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ function copyImpl(source, target, targetStart, sourceStart, sourceEnd) {
267267
return _copyActual(source, target, targetStart, sourceStart, sourceEnd);
268268
}
269269

270-
function _copyActual(source, target, targetStart, sourceStart, sourceEnd, isUint8Copy = false) {
270+
function _copyActual(source, target, targetStart, sourceStart, sourceEnd) {
271271
if (sourceEnd - sourceStart > target.byteLength - targetStart)
272272
sourceEnd = sourceStart + target.byteLength - targetStart;
273273

@@ -279,11 +279,7 @@ function _copyActual(source, target, targetStart, sourceStart, sourceEnd, isUint
279279
if (nb <= 0)
280280
return 0;
281281

282-
if (sourceStart === 0 && nb === sourceLen && (isUint8Copy || isUint8Array(target))) {
283-
TypedArrayPrototypeSet(target, source, targetStart);
284-
} else {
285-
_copy(source, target, targetStart, sourceStart, nb);
286-
}
282+
_copy(source, target, targetStart, sourceStart, nb);
287283

288284
return nb;
289285
}

src/node_buffer.cc

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -585,26 +585,15 @@ void StringSlice(const FunctionCallbackInfo<Value>& args) {
585585
}
586586
}
587587

588-
void CopyImpl(Local<Value> source_obj,
589-
Local<Value> target_obj,
590-
const uint32_t target_start,
591-
const uint32_t source_start,
592-
const uint32_t to_copy) {
593-
ArrayBufferViewContents<char> source(source_obj);
594-
SPREAD_BUFFER_ARG(target_obj, target);
595-
596-
memmove(target_data + target_start, source.data() + source_start, to_copy);
597-
}
598-
599588
// Assume caller has properly validated args.
600589
void SlowCopy(const FunctionCallbackInfo<Value>& args) {
601-
Local<Value> source_obj = args[0];
602-
Local<Value> target_obj = args[1];
603590
const uint32_t target_start = args[2].As<Uint32>()->Value();
604591
const uint32_t source_start = args[3].As<Uint32>()->Value();
605592
const uint32_t to_copy = args[4].As<Uint32>()->Value();
606593

607-
CopyImpl(source_obj, target_obj, target_start, source_start, to_copy);
594+
ArrayBufferView::FastCopy(ArrayBufferView::Cast(*args[0]), source_start,
595+
ArrayBufferView::Cast(*args[1]), target_start,
596+
to_copy);
608597

609598
args.GetReturnValue().Set(to_copy);
610599
}
@@ -618,10 +607,10 @@ uint32_t FastCopy(Local<Value> receiver,
618607
uint32_t to_copy,
619608
// NOLINTNEXTLINE(runtime/references)
620609
FastApiCallbackOptions& options) {
621-
HandleScope scope(options.isolate);
622-
623-
CopyImpl(source_obj, target_obj, target_start, source_start, to_copy);
624-
610+
TRACK_V8_FAST_API_CALL("buffer.copy");
611+
ArrayBufferView::FastCopy(ArrayBufferView::Cast(*source_obj), source_start,
612+
ArrayBufferView::Cast(*target_obj), target_start,
613+
to_copy);
625614
return to_copy;
626615
}
627616

0 commit comments

Comments
 (0)