diff --git a/benchmark/buffers/buffer-copy.js b/benchmark/buffers/buffer-copy.js new file mode 100644 index 00000000000000..92c33c89799d9a --- /dev/null +++ b/benchmark/buffers/buffer-copy.js @@ -0,0 +1,23 @@ +'use strict'; +const common = require('../common.js'); +const { randomBytes } = require('crypto'); + +const KB = 1024; + +const bench = common.createBenchmark(main, { + size: [10, KB, 2 * KB, 8 * KB, 64 * KB /* , 256 * KB */], + // This option checks edge case when target.length !== source.length. + targetStart: [0, 1], + n: [1024] +}); + +function main({ n, size, targetStart }) { + const source = randomBytes(size); + const target = randomBytes(size); + + bench.start(); + for (var i = 0; i < n * 1024; i++) { + source.copy(target, targetStart); + } + bench.end(n); +} diff --git a/lib/buffer.js b/lib/buffer.js index b032736f509e5c..c168c7c2a31a54 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -22,8 +22,8 @@ 'use strict'; const { - byteLengthUtf8, copy: _copy, + byteLengthUtf8, compare: _compare, compareOffset, createFromString, @@ -401,7 +401,7 @@ function fromObject(obj) { if (b.length === 0) return b; - _copy(obj, b, 0, 0, obj.length); + fastcopy(obj, b, 0, 0, obj.length); return b; } @@ -473,7 +473,7 @@ Buffer.concat = function concat(list, length) { throw new ERR_INVALID_ARG_TYPE( `list[${i}]`, ['Array', 'Buffer', 'Uint8Array'], list[i]); } - _copy(buf, buffer, pos); + fastcopy(buf, buffer, pos); pos += buf.length; } @@ -488,6 +488,59 @@ Buffer.concat = function concat(list, length) { return buffer; }; +function fastcopy(source, target, targetStart = 0, sourceStart = 0, sourceEnd) { + if (!isUint8Array(source)) { + throw new ERR_INVALID_ARG_TYPE('source', ['Buffer', 'Uint8Array'], source); + } + + if (!isUint8Array(target)) { + throw new ERR_INVALID_ARG_TYPE('target', ['Buffer', 'Uint8Array'], target); + } + + if (targetStart < 0) + throw new ERR_OUT_OF_RANGE('targetStart', '>= 0', targetStart); + targetStart >>>= 0; + + if (sourceStart < 0) + throw new ERR_OUT_OF_RANGE('sourceStart', '>= 0', sourceStart); + sourceStart >>>= 0; + + if (sourceEnd === undefined) + sourceEnd = source.byteLength; + else if (sourceEnd < 0) + throw new ERR_OUT_OF_RANGE('sourceEnd', '>= 0', sourceEnd); + else + sourceEnd >>>= 0; + + if (targetStart >= target.byteLength || sourceStart >= sourceEnd) + return 0; + + if (sourceStart > source.byteLength) { + throw new ERR_OUT_OF_RANGE( + 'sourceStart', `<= ${source.byteLength}`, sourceStart + ); + } + + const targetBytesAmount = target.byteLength - targetStart; + + if (sourceEnd - sourceStart > targetBytesAmount) + sourceEnd = sourceStart + targetBytesAmount; + + const bytesAmount = Math.min( + sourceEnd - sourceStart, + targetBytesAmount, + source.byteLength - sourceStart + ); + + if (sourceStart > 0 || bytesAmount < source.byteLength) { + sourceEnd = sourceStart + bytesAmount; + return _copy(source, target, targetStart, sourceStart, sourceEnd); + } + + target.set(source, targetStart); + return bytesAmount; +} + function base64ByteLength(str, bytes) { // Handle padding if (str.charCodeAt(bytes - 1) === 0x3D) @@ -626,7 +679,7 @@ function stringSlice(buf, encoding, start, end) { Buffer.prototype.copy = function copy(target, targetStart, sourceStart, sourceEnd) { - return _copy(this, target, targetStart, sourceStart, sourceEnd); + return fastcopy(this, target, targetStart, sourceStart, sourceEnd); }; // No need to verify that "buf.length <= MAX_UINT32" since it's a read-only diff --git a/test/parallel/test-buffer-alloc.js b/test/parallel/test-buffer-alloc.js index cee87994965493..9dc68f6e5d357e 100644 --- a/test/parallel/test-buffer-alloc.js +++ b/test/parallel/test-buffer-alloc.js @@ -964,7 +964,8 @@ common.expectsError( { code: 'ERR_INVALID_ARG_TYPE', type: TypeError, - message: 'argument must be a buffer' + message: 'The "target" argument must be one of type Buffer or ' + + 'Uint8Array. Received type undefined' }); assert.throws(() => Buffer.from(), { @@ -1009,13 +1010,6 @@ assert.strictEqual(SlowBuffer.prototype.offset, undefined); assert.throws(() => Buffer.from(new ArrayBuffer(0), -1 >>> 0), errMsg); } -// ParseArrayIndex() should reject values that don't fit in a 32 bits size_t. -common.expectsError(() => { - const a = Buffer.alloc(1); - const b = Buffer.alloc(1); - a.copy(b, 0, 0x100000000, 0x100000001); -}, outOfRangeError); - // Unpooled buffer (replaces SlowBuffer) { const ubuf = Buffer.allocUnsafeSlow(10); diff --git a/test/parallel/test-buffer-copy.js b/test/parallel/test-buffer-copy.js index e9f789a88623e7..e7d55fa31c6e82 100644 --- a/test/parallel/test-buffer-copy.js +++ b/test/parallel/test-buffer-copy.js @@ -9,7 +9,8 @@ const c = Buffer.allocUnsafe(512); const errorProperty = { code: 'ERR_OUT_OF_RANGE', type: RangeError, - message: 'Index out of range' + message: 'The value of "sourceStart" is out of range. ' + + 'It must be >= 0. Received -1' }; let cntr = 0;