Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e2f03c8

Browse filesBrowse files
thisalihassanaduh95
authored andcommitted
buffer: improve performance of multiple Buffer operations
- copyBytesFrom: calculate byte offsets directly instead of slicing into an intermediate typed array - toString('hex'): use V8 Uint8Array.prototype.toHex() builtin - fill: add single-char ASCII fast path - indexOf: use indexOfString directly for ASCII encoding - swap16/32/64: add V8 Fast API functions PR-URL: #61871 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 2c63d30 commit e2f03c8
Copy full SHA for e2f03c8

10 files changed

+280-70Lines changed: 280 additions & 70 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎benchmark/buffers/buffer-bytelength-string.js‎

Copy file name to clipboardExpand all lines: benchmark/buffers/buffer-bytelength-string.js
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const common = require('../common');
44
const bench = common.createBenchmark(main, {
55
type: ['one_byte', 'two_bytes', 'three_bytes',
66
'four_bytes', 'latin1'],
7-
encoding: ['utf8', 'base64'],
7+
encoding: ['utf8', 'base64', 'latin1', 'hex'],
88
repeat: [1, 2, 16, 256], // x16
99
n: [4e6],
1010
});
Collapse file
+31Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
5+
const bench = common.createBenchmark(main, {
6+
type: ['Uint8Array', 'Uint16Array', 'Uint32Array', 'Float64Array'],
7+
len: [64, 256, 2048],
8+
partial: ['none', 'offset', 'offset-length'],
9+
n: [6e5],
10+
});
11+
12+
function main({ n, len, type, partial }) {
13+
const TypedArrayCtor = globalThis[type];
14+
const src = new TypedArrayCtor(len);
15+
for (let i = 0; i < len; i++) src[i] = i;
16+
17+
let offset;
18+
let length;
19+
if (partial === 'offset') {
20+
offset = len >>> 2;
21+
} else if (partial === 'offset-length') {
22+
offset = len >>> 2;
23+
length = len >>> 1;
24+
}
25+
26+
bench.start();
27+
for (let i = 0; i < n; i++) {
28+
Buffer.copyBytesFrom(src, offset, length);
29+
}
30+
bench.end(n);
31+
}
Collapse file

‎benchmark/buffers/buffer-fill.js‎

Copy file name to clipboardExpand all lines: benchmark/buffers/buffer-fill.js
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const bench = common.createBenchmark(main, {
1010
'fill("t")',
1111
'fill("test")',
1212
'fill("t", "utf8")',
13+
'fill("t", "ascii")',
1314
'fill("t", 0, "utf8")',
1415
'fill("t", 0)',
1516
'fill(Buffer.alloc(1), 0)',
Collapse file

‎benchmark/buffers/buffer-indexof.js‎

Copy file name to clipboardExpand all lines: benchmark/buffers/buffer-indexof.js
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const searchStrings = [
1919

2020
const bench = common.createBenchmark(main, {
2121
search: searchStrings,
22-
encoding: ['undefined', 'utf8', 'ucs2', 'latin1'],
22+
encoding: ['undefined', 'utf8', 'ascii', 'latin1', 'ucs2'],
2323
type: ['buffer', 'string'],
2424
n: [5e4],
2525
}, {
Collapse file

‎benchmark/buffers/buffer-tostring.js‎

Copy file name to clipboardExpand all lines: benchmark/buffers/buffer-tostring.js
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const common = require('../common.js');
44

55
const bench = common.createBenchmark(main, {
6-
encoding: ['', 'utf8', 'ascii', 'latin1', 'hex', 'UCS-2'],
6+
encoding: ['', 'utf8', 'ascii', 'latin1', 'hex', 'base64', 'base64url', 'UCS-2'],
77
args: [0, 1, 3],
88
len: [1, 64, 1024],
99
n: [1e6],
Collapse file

‎lib/buffer.js‎

Copy file name to clipboardExpand all lines: lib/buffer.js
+67-54Lines changed: 67 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ const {
5050
TypedArrayPrototypeGetByteOffset,
5151
TypedArrayPrototypeGetLength,
5252
TypedArrayPrototypeSet,
53-
TypedArrayPrototypeSlice,
5453
TypedArrayPrototypeSubarray,
5554
Uint8Array,
5655
} = primordials;
@@ -383,28 +382,33 @@ Buffer.copyBytesFrom = function copyBytesFrom(view, offset, length) {
383382
return new FastBuffer();
384383
}
385384

386-
if (offset !== undefined || length !== undefined) {
387-
if (offset !== undefined) {
388-
validateInteger(offset, 'offset', 0);
389-
if (offset >= viewLength) return new FastBuffer();
390-
} else {
391-
offset = 0;
392-
}
393-
let end;
394-
if (length !== undefined) {
395-
validateInteger(length, 'length', 0);
396-
end = offset + length;
397-
} else {
398-
end = viewLength;
399-
}
385+
let start = 0;
386+
let end = viewLength;
400387

401-
view = TypedArrayPrototypeSlice(view, offset, end);
388+
if (offset !== undefined) {
389+
validateInteger(offset, 'offset', 0);
390+
if (offset >= viewLength) return new FastBuffer();
391+
start = offset;
402392
}
403393

394+
if (length !== undefined) {
395+
validateInteger(length, 'length', 0);
396+
// The old code used TypedArrayPrototypeSlice which clamps internally.
397+
end = MathMin(start + length, viewLength);
398+
}
399+
400+
if (end <= start) return new FastBuffer();
401+
402+
const viewByteLength = TypedArrayPrototypeGetByteLength(view);
403+
const elementSize = viewByteLength / viewLength;
404+
const srcByteOffset = TypedArrayPrototypeGetByteOffset(view) +
405+
start * elementSize;
406+
const srcByteLength = (end - start) * elementSize;
407+
404408
return fromArrayLike(new Uint8Array(
405409
TypedArrayPrototypeGetBuffer(view),
406-
TypedArrayPrototypeGetByteOffset(view),
407-
TypedArrayPrototypeGetByteLength(view)));
410+
srcByteOffset,
411+
srcByteLength));
408412
};
409413

410414
// Identical to the built-in %TypedArray%.of(), but avoids using the deprecated
@@ -551,14 +555,15 @@ function fromArrayBuffer(obj, byteOffset, length) {
551555
}
552556

553557
function fromArrayLike(obj) {
554-
if (obj.length <= 0)
558+
const { length } = obj;
559+
if (length <= 0)
555560
return new FastBuffer();
556-
if (obj.length < (Buffer.poolSize >>> 1)) {
557-
if (obj.length > (poolSize - poolOffset))
561+
if (length < (Buffer.poolSize >>> 1)) {
562+
if (length > (poolSize - poolOffset))
558563
createPool();
559-
const b = new FastBuffer(allocPool, poolOffset, obj.length);
564+
const b = new FastBuffer(allocPool, poolOffset, length);
560565
TypedArrayPrototypeSet(b, obj, 0);
561-
poolOffset += obj.length;
566+
poolOffset += length;
562567
alignPool();
563568
return b;
564569
}
@@ -732,11 +737,7 @@ const encodingOps = {
732737
write: asciiWrite,
733738
slice: asciiSlice,
734739
indexOf: (buf, val, byteOffset, dir) =>
735-
indexOfBuffer(buf,
736-
fromStringFast(val, encodingOps.ascii),
737-
byteOffset,
738-
encodingsMap.ascii,
739-
dir),
740+
indexOfString(buf, val, byteOffset, encodingsMap.ascii, dir),
740741
},
741742
base64: {
742743
encoding: 'base64',
@@ -897,17 +898,17 @@ Buffer.prototype.toString = function toString(encoding, start, end) {
897898
return utf8Slice(this, 0, this.length);
898899
}
899900

900-
const len = this.length;
901+
const bufferLength = TypedArrayPrototypeGetLength(this);
901902

902903
if (start <= 0)
903904
start = 0;
904-
else if (start >= len)
905+
else if (start >= bufferLength)
905906
return '';
906907
else
907908
start = MathTrunc(start) || 0;
908909

909-
if (end === undefined || end > len)
910-
end = len;
910+
if (end === undefined || end > bufferLength)
911+
end = bufferLength;
911912
else
912913
end = MathTrunc(end) || 0;
913914

@@ -1118,7 +1119,9 @@ function _fill(buf, value, offset, end, encoding) {
11181119
value = 0;
11191120
} else if (value.length === 1) {
11201121
// Fast path: If `value` fits into a single byte, use that numeric value.
1121-
if (normalizedEncoding === 'utf8') {
1122+
// ASCII shares this branch with utf8 since code < 128 covers the full
1123+
// ASCII range; anything outside falls through to C++ bindingFill.
1124+
if (normalizedEncoding === 'utf8' || normalizedEncoding === 'ascii') {
11221125
const code = StringPrototypeCharCodeAt(value, 0);
11231126
if (code < 128) {
11241127
value = code;
@@ -1168,29 +1171,30 @@ function _fill(buf, value, offset, end, encoding) {
11681171
}
11691172

11701173
Buffer.prototype.write = function write(string, offset, length, encoding) {
1174+
const bufferLength = TypedArrayPrototypeGetLength(this);
11711175
// Buffer#write(string);
11721176
if (offset === undefined) {
1173-
return utf8Write(this, string, 0, this.length);
1177+
return utf8Write(this, string, 0, bufferLength);
11741178
}
11751179
// Buffer#write(string, encoding)
11761180
if (length === undefined && typeof offset === 'string') {
11771181
encoding = offset;
1178-
length = this.length;
1182+
length = bufferLength;
11791183
offset = 0;
11801184

11811185
// Buffer#write(string, offset[, length][, encoding])
11821186
} else {
1183-
validateOffset(offset, 'offset', 0, this.length);
1187+
validateOffset(offset, 'offset', 0, bufferLength);
11841188

1185-
const remaining = this.length - offset;
1189+
const remaining = bufferLength - offset;
11861190

11871191
if (length === undefined) {
11881192
length = remaining;
11891193
} else if (typeof length === 'string') {
11901194
encoding = length;
11911195
length = remaining;
11921196
} else {
1193-
validateOffset(length, 'length', 0, this.length);
1197+
validateOffset(length, 'length', 0, bufferLength);
11941198
if (length > remaining)
11951199
length = remaining;
11961200
}
@@ -1208,9 +1212,10 @@ Buffer.prototype.write = function write(string, offset, length, encoding) {
12081212
};
12091213

12101214
Buffer.prototype.toJSON = function toJSON() {
1211-
if (this.length > 0) {
1212-
const data = new Array(this.length);
1213-
for (let i = 0; i < this.length; ++i)
1215+
const bufferLength = TypedArrayPrototypeGetLength(this);
1216+
if (bufferLength > 0) {
1217+
const data = new Array(bufferLength);
1218+
for (let i = 0; i < bufferLength; ++i)
12141219
data[i] = this[i];
12151220
return { type: 'Buffer', data };
12161221
}
@@ -1235,7 +1240,7 @@ function adjustOffset(offset, length) {
12351240
}
12361241

12371242
Buffer.prototype.subarray = function subarray(start, end) {
1238-
const srcLength = this.length;
1243+
const srcLength = TypedArrayPrototypeGetLength(this);
12391244
start = adjustOffset(start, srcLength);
12401245
end = end !== undefined ? adjustOffset(end, srcLength) : srcLength;
12411246
const newLength = end > start ? end - start : 0;
@@ -1253,45 +1258,52 @@ function swap(b, n, m) {
12531258
}
12541259

12551260
Buffer.prototype.swap16 = function swap16() {
1256-
// For Buffer.length < 128, it's generally faster to
1261+
// Ref: https://github.com/nodejs/node/pull/61871#discussion_r2889557696
1262+
// For Buffer.length <= 32, it's generally faster to
12571263
// do the swap in javascript. For larger buffers,
12581264
// dropping down to the native code is faster.
1259-
const len = this.length;
1265+
const len = TypedArrayPrototypeGetLength(this);
12601266
if (len % 2 !== 0)
12611267
throw new ERR_INVALID_BUFFER_SIZE('16-bits');
1262-
if (len < 128) {
1268+
if (len <= 32) {
12631269
for (let i = 0; i < len; i += 2)
12641270
swap(this, i, i + 1);
12651271
return this;
12661272
}
1267-
return _swap16(this);
1273+
_swap16(this);
1274+
return this;
12681275
};
12691276

12701277
Buffer.prototype.swap32 = function swap32() {
1271-
// For Buffer.length < 192, it's generally faster to
1278+
// Ref: https://github.com/nodejs/node/pull/61871#discussion_r2889557696
1279+
// For Buffer.length <= 32, it's generally faster to
12721280
// do the swap in javascript. For larger buffers,
12731281
// dropping down to the native code is faster.
1274-
const len = this.length;
1282+
const len = TypedArrayPrototypeGetLength(this);
12751283
if (len % 4 !== 0)
12761284
throw new ERR_INVALID_BUFFER_SIZE('32-bits');
1277-
if (len < 192) {
1285+
if (len <= 32) {
12781286
for (let i = 0; i < len; i += 4) {
12791287
swap(this, i, i + 3);
12801288
swap(this, i + 1, i + 2);
12811289
}
12821290
return this;
12831291
}
1284-
return _swap32(this);
1292+
_swap32(this);
1293+
return this;
12851294
};
12861295

12871296
Buffer.prototype.swap64 = function swap64() {
1288-
// For Buffer.length < 192, it's generally faster to
1297+
// Ref: https://github.com/nodejs/node/pull/61871#discussion_r2889557696
1298+
// For Buffer.length < 48, it's generally faster to
12891299
// do the swap in javascript. For larger buffers,
12901300
// dropping down to the native code is faster.
1291-
const len = this.length;
1301+
// Threshold differs from swap16/swap32 (<=32) because swap64's
1302+
// crossover is between 40 and 48 (native wins at 48, loses at 40).
1303+
const len = TypedArrayPrototypeGetLength(this);
12921304
if (len % 8 !== 0)
12931305
throw new ERR_INVALID_BUFFER_SIZE('64-bits');
1294-
if (len < 192) {
1306+
if (len < 48) {
12951307
for (let i = 0; i < len; i += 8) {
12961308
swap(this, i, i + 7);
12971309
swap(this, i + 1, i + 6);
@@ -1300,7 +1312,8 @@ Buffer.prototype.swap64 = function swap64() {
13001312
}
13011313
return this;
13021314
}
1303-
return _swap64(this);
1315+
_swap64(this);
1316+
return this;
13041317
};
13051318

13061319
Buffer.prototype.toLocaleString = Buffer.prototype.toString;

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.