From 0152f4a94b722e39aaee2b1eddc98df5522988f4 Mon Sep 17 00:00:00 2001 From: reklatsmasters Date: Wed, 12 Dec 2018 03:38:20 +0500 Subject: [PATCH 1/5] buffer: up to 2x times faster copy of buffers --- benchmark/buffers/buffer-copy.js | 18 ++++++++++ lib/buffer.js | 57 ++++++++++++++++++++++++++++-- test/parallel/test-buffer-alloc.js | 10 ++---- test/parallel/test-buffer-copy.js | 3 +- 4 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 benchmark/buffers/buffer-copy.js diff --git a/benchmark/buffers/buffer-copy.js b/benchmark/buffers/buffer-copy.js new file mode 100644 index 00000000000000..aa588b44c5264a --- /dev/null +++ b/benchmark/buffers/buffer-copy.js @@ -0,0 +1,18 @@ +'use strict'; +const common = require('../common.js'); + +const bench = common.createBenchmark(main, { + size: [10, 1024, 2048, 4096, 8192], + n: [1024] +}); + +function main({ n, size }) { + const source = Buffer.allocUnsafe(size); + const target = Buffer.allocUnsafe(size); + + bench.start(); + for (var i = 0; i < n * 1024; i++) { + source.copy(target); + } + bench.end(n); +} diff --git a/lib/buffer.js b/lib/buffer.js index b032736f509e5c..11a9a4b9a6cf45 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -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); + else + targetStart >>>= 0; + + if (sourceStart < 0) + throw new ERR_OUT_OF_RANGE('sourceStart', '>= 0', sourceStart); + else + 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 + ); + } + + if (sourceEnd - sourceStart > target.byteLength - targetStart) + sourceEnd = sourceStart + target.byteLength - targetStart; + + const to_copy = Math.min( + Math.min( + sourceEnd - sourceStart, target.byteLength - targetStart + ), + source.byteLength - sourceStart + ); + + if (sourceStart > 0 || to_copy < source.byteLength) { + source = source.subarray(sourceStart, sourceStart + to_copy); + } + + target.set(source, targetStart); + return to_copy; +} + 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; From 655a9a5a7ba45363bc13b4cf1c057559a490313e Mon Sep 17 00:00:00 2001 From: Jeremiah Senkpiel Date: Fri, 14 Dec 2018 05:53:03 +0500 Subject: [PATCH 2/5] Update benchmark/buffers/buffer-copy.js Co-Authored-By: reklatsmasters --- benchmark/buffers/buffer-copy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/buffers/buffer-copy.js b/benchmark/buffers/buffer-copy.js index aa588b44c5264a..2f0e965aa6e715 100644 --- a/benchmark/buffers/buffer-copy.js +++ b/benchmark/buffers/buffer-copy.js @@ -2,7 +2,7 @@ const common = require('../common.js'); const bench = common.createBenchmark(main, { - size: [10, 1024, 2048, 4096, 8192], + size: [10, 1024, 2048, 8192, 64 * 1024, 256 * 1024, 1024 * 1024, 8 * 1024 * 1024, 32 * 1024 * 1024], n: [1024] }); From 15dfef30b077338473cc6b3e33871de532473d1e Mon Sep 17 00:00:00 2001 From: reklatsmasters Date: Fri, 14 Dec 2018 06:00:43 +0500 Subject: [PATCH 3/5] update --- benchmark/buffers/buffer-copy.js | 7 ++++--- lib/buffer.js | 28 +++++++++++++--------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/benchmark/buffers/buffer-copy.js b/benchmark/buffers/buffer-copy.js index 2f0e965aa6e715..22b5923b418429 100644 --- a/benchmark/buffers/buffer-copy.js +++ b/benchmark/buffers/buffer-copy.js @@ -1,5 +1,6 @@ 'use strict'; const common = require('../common.js'); +const { randomFillSync } = require('crypto'); const bench = common.createBenchmark(main, { size: [10, 1024, 2048, 8192, 64 * 1024, 256 * 1024, 1024 * 1024, 8 * 1024 * 1024, 32 * 1024 * 1024], @@ -7,12 +8,12 @@ const bench = common.createBenchmark(main, { }); function main({ n, size }) { - const source = Buffer.allocUnsafe(size); - const target = Buffer.allocUnsafe(size); + const buf = Buffer.allocUnsafe(size * 2); + randomFillSync(buf); bench.start(); for (var i = 0; i < n * 1024; i++) { - source.copy(target); + buf.copy(buf, size, 0, size); } bench.end(n); } diff --git a/lib/buffer.js b/lib/buffer.js index 11a9a4b9a6cf45..098dc38e3ed55b 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -23,7 +23,6 @@ const { byteLengthUtf8, - copy: _copy, compare: _compare, compareOffset, createFromString, @@ -401,7 +400,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; } @@ -499,13 +498,11 @@ function fastcopy(source, target, targetStart = 0, sourceStart = 0, sourceEnd) { if (targetStart < 0) throw new ERR_OUT_OF_RANGE('targetStart', '>= 0', targetStart); - else - targetStart >>>= 0; + targetStart >>>= 0; if (sourceStart < 0) throw new ERR_OUT_OF_RANGE('sourceStart', '>= 0', sourceStart); - else - sourceStart >>>= 0; + sourceStart >>>= 0; if (sourceEnd === undefined) sourceEnd = source.byteLength; @@ -523,22 +520,23 @@ function fastcopy(source, target, targetStart = 0, sourceStart = 0, sourceEnd) { ); } - if (sourceEnd - sourceStart > target.byteLength - targetStart) - sourceEnd = sourceStart + target.byteLength - targetStart; + const targetBytesAmount = target.byteLength - targetStart; + + if (sourceEnd - sourceStart > targetBytesAmount) + sourceEnd = sourceStart + targetBytesAmount; - const to_copy = Math.min( - Math.min( - sourceEnd - sourceStart, target.byteLength - targetStart - ), + const bytesAmount = Math.min( + sourceEnd - sourceStart, + targetBytesAmount, source.byteLength - sourceStart ); - if (sourceStart > 0 || to_copy < source.byteLength) { - source = source.subarray(sourceStart, sourceStart + to_copy); + if (sourceStart > 0 || bytesAmount < source.byteLength) { + source = source.subarray(sourceStart, sourceStart + bytesAmount); } target.set(source, targetStart); - return to_copy; + return bytesAmount; } function base64ByteLength(str, bytes) { From 0db3b6437d56976b95894f78a794d5c8cacdf601 Mon Sep 17 00:00:00 2001 From: reklatsmasters Date: Fri, 14 Dec 2018 06:37:00 +0500 Subject: [PATCH 4/5] improve benchmark --- benchmark/buffers/buffer-copy.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/benchmark/buffers/buffer-copy.js b/benchmark/buffers/buffer-copy.js index 22b5923b418429..364f334d24efcd 100644 --- a/benchmark/buffers/buffer-copy.js +++ b/benchmark/buffers/buffer-copy.js @@ -1,16 +1,18 @@ 'use strict'; const common = require('../common.js'); -const { randomFillSync } = require('crypto'); +const { randomBytes } = require('crypto'); + +const KB = 1024; +const MB = KB * KB; const bench = common.createBenchmark(main, { - size: [10, 1024, 2048, 8192, 64 * 1024, 256 * 1024, 1024 * 1024, 8 * 1024 * 1024, 32 * 1024 * 1024], + size: [10, KB, 2 * KB, 8 * KB, 64 * KB, 256 * KB, MB, 8 * MB, 32 * MB], n: [1024] }); -function main({ n, size }) { - const buf = Buffer.allocUnsafe(size * 2); - randomFillSync(buf); +const buf = randomBytes(32 * MB * 2); +function main({ n, size }) { bench.start(); for (var i = 0; i < n * 1024; i++) { buf.copy(buf, size, 0, size); From 158e5842363c483e632b501a91b63810e405dab7 Mon Sep 17 00:00:00 2001 From: reklatsmasters Date: Fri, 14 Dec 2018 09:49:03 +0500 Subject: [PATCH 5/5] fix edge case --- benchmark/buffers/buffer-copy.js | 12 +++++++----- lib/buffer.js | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/benchmark/buffers/buffer-copy.js b/benchmark/buffers/buffer-copy.js index 364f334d24efcd..92c33c89799d9a 100644 --- a/benchmark/buffers/buffer-copy.js +++ b/benchmark/buffers/buffer-copy.js @@ -3,19 +3,21 @@ const common = require('../common.js'); const { randomBytes } = require('crypto'); const KB = 1024; -const MB = KB * KB; const bench = common.createBenchmark(main, { - size: [10, KB, 2 * KB, 8 * KB, 64 * KB, 256 * KB, MB, 8 * MB, 32 * MB], + 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] }); -const buf = randomBytes(32 * MB * 2); +function main({ n, size, targetStart }) { + const source = randomBytes(size); + const target = randomBytes(size); -function main({ n, size }) { bench.start(); for (var i = 0; i < n * 1024; i++) { - buf.copy(buf, size, 0, size); + source.copy(target, targetStart); } bench.end(n); } diff --git a/lib/buffer.js b/lib/buffer.js index 098dc38e3ed55b..c168c7c2a31a54 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -22,6 +22,7 @@ 'use strict'; const { + copy: _copy, byteLengthUtf8, compare: _compare, compareOffset, @@ -532,7 +533,8 @@ function fastcopy(source, target, targetStart = 0, sourceStart = 0, sourceEnd) { ); if (sourceStart > 0 || bytesAmount < source.byteLength) { - source = source.subarray(sourceStart, sourceStart + bytesAmount); + sourceEnd = sourceStart + bytesAmount; + return _copy(source, target, targetStart, sourceStart, sourceEnd); } target.set(source, targetStart);