Repository: nodejs/node
Pull request: buffer: speed up concat via TypedArray#set

Huge win by avoiding complex `_copyActual` logic when we can copy the whole source into target. The native copy implementation is faster only for partial copies.

Before:

```sh
buffers/buffer-concat-fill.js
buffers/buffer-concat-fill.js n=800000 extraSize=1: 3,080,309.438799309
buffers/buffer-concat-fill.js n=800000 extraSize=256: 2,718,494.3906977526
buffers/buffer-concat-fill.js n=800000 extraSize=1024: 2,592,930.9112553936

buffers/buffer-concat.js
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4: 6,895,296.007515873
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4: 6,850,915.824861098
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4: 6,714,498.121438699
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4: 6,606,244.795672606
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4: 3,875,370.506613865
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4: 4,427,107.007979794
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16: 2,202,621.1880456866
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16: 2,308,511.109946339
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16: 2,111,185.355049204
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16: 2,169,882.104202485
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16: 993,752.7633158871
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16: 1,010,066.0454915363
```

After:

```sh
buffers/buffer-concat-fill.js
buffers/buffer-concat-fill.js n=800000 extraSize=1: 4,213,313.932988885
buffers/buffer-concat-fill.js n=800000 extraSize=256: 3,999,625.0351529545
buffers/buffer-concat-fill.js n=800000 extraSize=1024: 3,271,098.409944823

buffers/buffer-concat.js
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4: 12,018,365.504695036
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4: 11,561,090.427597769
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4: 11,106,682.475733392
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4: 10,827,423.883277772
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4: 6,297,132.164792359
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4: 6,326,826.005617953
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16: 4,151,263.382265607
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16: 4,295,939.1963506
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16: 3,758,688.301977228
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16: 3,904,910.547041359
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16: 1,265,188.4389452678
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16: 1,284,207.4931866652
```

Changed files:
filename: lib/buffer.js
patch: @@ -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,11 @@ function _copyActual(source, target, targetStart, sourceStart, sourceEnd) {
   if (nb <= 0)
     return 0;
 
-  _copy(source, target, targetStart, sourceStart, nb);
+  if (sourceStart === 0 && nb === sourceLen && (isUint8Copy || isUint8Array(target))) {
+    TypedArrayPrototypeSet(target, source, targetStart);
+  } else {
+    _copy(source, target, targetStart, sourceStart, nb);
+  }
 
   return nb;
 }
@@ -600,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

filename: test/parallel/test-buffer-concat.js
patch: @@ -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);
 

filename: test/parallel/test-buffer-copy.js
patch: @@ -226,6 +226,23 @@ 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);
+  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));
+}
+
 {
   c.fill('C');
   assert.throws(() => {