From e10034f29d4e365ebf668339ca7f882a56077171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sat, 25 Oct 2025 02:28:10 +0200 Subject: [PATCH 01/10] buffer: speed up concat via TypedArray#set --- lib/buffer.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/buffer.js b/lib/buffer.js index c9f45d333886d3..8bfc575dfc8b2d 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -51,6 +51,7 @@ const { TypedArrayPrototypeGetLength, TypedArrayPrototypeSet, TypedArrayPrototypeSlice, + TypedArrayPrototypeSubarray, Uint8Array, } = primordials; @@ -600,7 +601,21 @@ Buffer.concat = function concat(list, length) { throw new ERR_INVALID_ARG_TYPE( `list[${i}]`, ['Buffer', 'Uint8Array'], list[i]); } - pos += _copyActual(buf, buffer, pos, 0, buf.length); + + if (buf.length === 0 || pos >= length) { + // Empty buf or destination is full; subsequent copies are no-ops. + // We don't break to preserve compatibility and throw if isUint8Array fails. + continue; + } + + const available = length - pos; + const toCopy = buf.length > available ? available : buf.length; + if (toCopy === buf.length) { + TypedArrayPrototypeSet(buffer, buf, pos); + } else { + TypedArrayPrototypeSet(buffer, TypedArrayPrototypeSubarray(buf, 0, toCopy), pos); + } + pos += toCopy; } // Note: `length` is always equal to `buffer.length` at this point From a3ef7d2087ec0d371c460b27886074d4039fdfc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sat, 25 Oct 2025 09:31:32 +0200 Subject: [PATCH 02/10] complete coverage --- test/parallel/test-buffer-concat.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/parallel/test-buffer-concat.js b/test/parallel/test-buffer-concat.js index 2e7541fb58b063..39e6ca7997ecba 100644 --- a/test/parallel/test-buffer-concat.js +++ b/test/parallel/test-buffer-concat.js @@ -76,6 +76,13 @@ assert.throws(() => { const random10 = common.hasCrypto ? require('crypto').randomBytes(10) : Buffer.alloc(10, 1); +const derived18 = Buffer.alloc(18); +for (let i = 0, j = 0; i < 18; i++) { + if (i < 10) + derived18[i] = random10[i]; + else + derived18[i] = random10[j++]; +} const empty = Buffer.alloc(0); assert.notDeepStrictEqual(random10, empty); @@ -85,6 +92,7 @@ assert.deepStrictEqual(Buffer.concat([], 100), empty); assert.deepStrictEqual(Buffer.concat([random10], 0), empty); assert.deepStrictEqual(Buffer.concat([random10], 10), random10); assert.deepStrictEqual(Buffer.concat([random10, random10], 10), random10); +assert.deepStrictEqual(Buffer.concat([random10, random10], 18), derived18); assert.deepStrictEqual(Buffer.concat([empty, random10]), random10); assert.deepStrictEqual(Buffer.concat([random10, empty, empty]), random10); From 1502f5cdbaa434f86f900dc6e78a7011e7bacd8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 26 Oct 2025 14:09:09 +0100 Subject: [PATCH 03/10] store bufLength in a variable --- lib/buffer.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/buffer.js b/lib/buffer.js index 8bfc575dfc8b2d..4a3c916ff715cb 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -583,8 +583,9 @@ Buffer.concat = function concat(list, length) { if (length === undefined) { length = 0; for (let i = 0; i < list.length; i++) { - if (list[i].length) { - length += list[i].length; + const bufLength = list[i].length; + if (bufLength) { + length += bufLength; } } } else { @@ -602,15 +603,16 @@ Buffer.concat = function concat(list, length) { `list[${i}]`, ['Buffer', 'Uint8Array'], list[i]); } - if (buf.length === 0 || pos >= length) { - // Empty buf or destination is full; subsequent copies are no-ops. + const bufLength = buf.length; + if (bufLength === 0 || pos >= length) { + // Empty buf, or destination is full and subsequent copies are no-ops. // We don't break to preserve compatibility and throw if isUint8Array fails. continue; } const available = length - pos; - const toCopy = buf.length > available ? available : buf.length; - if (toCopy === buf.length) { + const toCopy = bufLength > available ? available : bufLength; + if (toCopy === bufLength) { TypedArrayPrototypeSet(buffer, buf, pos); } else { TypedArrayPrototypeSet(buffer, TypedArrayPrototypeSubarray(buf, 0, toCopy), pos); From a5b268e772e630642af52ac260d4ad6d231e1db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 26 Oct 2025 17:56:56 +0100 Subject: [PATCH 04/10] fix compat issue --- lib/buffer.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/buffer.js b/lib/buffer.js index 4a3c916ff715cb..9be6345bf2d8a1 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -615,7 +615,12 @@ Buffer.concat = function concat(list, length) { if (toCopy === bufLength) { TypedArrayPrototypeSet(buffer, buf, pos); } else { - TypedArrayPrototypeSet(buffer, TypedArrayPrototypeSubarray(buf, 0, toCopy), pos); + const src = new Uint8Array( + TypedArrayPrototypeGetBuffer(buf), + TypedArrayPrototypeGetByteOffset(buf), + toCopy, + ); + TypedArrayPrototypeSet(buffer, src, pos); } pos += toCopy; } From 3062e7ce3cc1fbdcff6a9225685b8a132c698fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 26 Oct 2025 18:11:39 +0100 Subject: [PATCH 05/10] lint --- lib/buffer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/buffer.js b/lib/buffer.js index 9be6345bf2d8a1..c028639d0f0d62 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -51,7 +51,6 @@ const { TypedArrayPrototypeGetLength, TypedArrayPrototypeSet, TypedArrayPrototypeSlice, - TypedArrayPrototypeSubarray, Uint8Array, } = primordials; From 34adb7cd25098a3026de7f8277643e6310f96b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 26 Oct 2025 19:43:10 +0100 Subject: [PATCH 06/10] simplify --- lib/buffer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/buffer.js b/lib/buffer.js index c028639d0f0d62..ec99db054e1937 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -615,8 +615,8 @@ Buffer.concat = function concat(list, length) { TypedArrayPrototypeSet(buffer, buf, pos); } else { const src = new Uint8Array( - TypedArrayPrototypeGetBuffer(buf), - TypedArrayPrototypeGetByteOffset(buf), + buf.buffer, + buf.byteOffset, toCopy, ); TypedArrayPrototypeSet(buffer, src, pos); From ffd22c1d96cde82c1bf9e8484c971c6085bbc910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 26 Oct 2025 21:00:08 +0100 Subject: [PATCH 07/10] Revert "simplify" This reverts commit 34adb7cd25098a3026de7f8277643e6310f96b46. --- lib/buffer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/buffer.js b/lib/buffer.js index ec99db054e1937..c028639d0f0d62 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -615,8 +615,8 @@ Buffer.concat = function concat(list, length) { TypedArrayPrototypeSet(buffer, buf, pos); } else { const src = new Uint8Array( - buf.buffer, - buf.byteOffset, + TypedArrayPrototypeGetBuffer(buf), + TypedArrayPrototypeGetByteOffset(buf), toCopy, ); TypedArrayPrototypeSet(buffer, src, pos); From 0a50b68e1f0f6344df5ec249b63c476282d82ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 26 Oct 2025 22:28:26 +0100 Subject: [PATCH 08/10] alternative implementation Co-authored-by: Nikita Skovoroda --- lib/buffer.js | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/lib/buffer.js b/lib/buffer.js index c028639d0f0d62..438dab5aece80a 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -252,7 +252,11 @@ function _copyActual(source, target, targetStart, sourceStart, sourceEnd) { if (nb <= 0) return 0; - _copy(source, target, targetStart, sourceStart, nb); + if (sourceStart === 0 && nb === sourceLen) { + TypedArrayPrototypeSet(target, source, targetStart); + } else { + _copy(source, target, targetStart, sourceStart, nb); + } return nb; } @@ -582,9 +586,8 @@ Buffer.concat = function concat(list, length) { if (length === undefined) { length = 0; for (let i = 0; i < list.length; i++) { - const bufLength = list[i].length; - if (bufLength) { - length += bufLength; + if (list[i].length) { + length += list[i].length; } } } else { @@ -601,27 +604,7 @@ Buffer.concat = function concat(list, length) { throw new ERR_INVALID_ARG_TYPE( `list[${i}]`, ['Buffer', 'Uint8Array'], list[i]); } - - const bufLength = buf.length; - if (bufLength === 0 || pos >= length) { - // Empty buf, or destination is full and subsequent copies are no-ops. - // We don't break to preserve compatibility and throw if isUint8Array fails. - continue; - } - - const available = length - pos; - const toCopy = bufLength > available ? available : bufLength; - if (toCopy === bufLength) { - TypedArrayPrototypeSet(buffer, buf, pos); - } else { - const src = new Uint8Array( - TypedArrayPrototypeGetBuffer(buf), - TypedArrayPrototypeGetByteOffset(buf), - toCopy, - ); - TypedArrayPrototypeSet(buffer, src, pos); - } - pos += toCopy; + pos += _copyActual(buf, buffer, pos, 0, buf.length); } // Note: `length` is always equal to `buffer.length` at this point From 7e75cf42a635cd0a798ae79240f0df21b012132f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Mon, 27 Oct 2025 00:06:53 +0100 Subject: [PATCH 09/10] add type check to set --- lib/buffer.js | 6 +++--- test/parallel/test-buffer-copy.js | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/buffer.js b/lib/buffer.js index 438dab5aece80a..d7d10007070e2c 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -240,7 +240,7 @@ function copyImpl(source, target, targetStart, sourceStart, sourceEnd) { return _copyActual(source, target, targetStart, sourceStart, sourceEnd); } -function _copyActual(source, target, targetStart, sourceStart, sourceEnd) { +function _copyActual(source, target, targetStart, sourceStart, sourceEnd, isUint8Copy = false) { if (sourceEnd - sourceStart > target.byteLength - targetStart) sourceEnd = sourceStart + target.byteLength - targetStart; @@ -252,7 +252,7 @@ function _copyActual(source, target, targetStart, sourceStart, sourceEnd) { if (nb <= 0) return 0; - if (sourceStart === 0 && nb === sourceLen) { + if (sourceStart === 0 && nb === sourceLen && (isUint8Copy || isUint8Array(target))) { TypedArrayPrototypeSet(target, source, targetStart); } else { _copy(source, target, targetStart, sourceStart, nb); @@ -604,7 +604,7 @@ Buffer.concat = function concat(list, length) { throw new ERR_INVALID_ARG_TYPE( `list[${i}]`, ['Buffer', 'Uint8Array'], list[i]); } - pos += _copyActual(buf, buffer, pos, 0, buf.length); + pos += _copyActual(buf, buffer, pos, 0, buf.length, true); } // Note: `length` is always equal to `buffer.length` at this point diff --git a/test/parallel/test-buffer-copy.js b/test/parallel/test-buffer-copy.js index fef20578cfd396..db220e9078ca88 100644 --- a/test/parallel/test-buffer-copy.js +++ b/test/parallel/test-buffer-copy.js @@ -226,6 +226,16 @@ b.copy(c, 'not a valid offset'); // Make sure this acted like a regular copy with `0` offset. assert.deepStrictEqual(c, b.slice(0, c.length)); +// Copy into a Uint16Array target; bytes are packed into 16-bit elements. +{ + const x = new Uint16Array(4); + const buf = Buffer.of(1, 2, 3, 4); + const copied = buf.copy(x); + assert.strictEqual(copied, 4); + assert.ok(x instanceof Uint16Array); + assert.deepStrictEqual(Array.from(x), [513, 1027, 0, 0]); +} + { c.fill('C'); assert.throws(() => { From a8a5e19d0720c5037f457566a90ac8871c41f277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Wed, 29 Oct 2025 22:13:12 +0100 Subject: [PATCH 10/10] fix test --- test/parallel/test-buffer-copy.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-buffer-copy.js b/test/parallel/test-buffer-copy.js index db220e9078ca88..9864fc7c551715 100644 --- a/test/parallel/test-buffer-copy.js +++ b/test/parallel/test-buffer-copy.js @@ -233,7 +233,14 @@ assert.deepStrictEqual(c, b.slice(0, c.length)); const copied = buf.copy(x); assert.strictEqual(copied, 4); assert.ok(x instanceof Uint16Array); - assert.deepStrictEqual(Array.from(x), [513, 1027, 0, 0]); + const bytes = new Uint8Array(x.buffer, x.byteOffset, 4); + assert.deepStrictEqual(Array.from(bytes), [1, 2, 3, 4]); + const remaining = new Uint8Array( + x.buffer, + x.byteOffset + 4, + x.byteLength - 4 + ); + assert.ok(remaining.every((b) => b === 0)); } {