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 a6ced7d

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 f5f21d3 commit a6ced7d
Copy full SHA for a6ced7d

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
Uint8ArrayPrototype,
@@ -385,28 +384,33 @@ Buffer.copyBytesFrom = function copyBytesFrom(view, offset, length) {
385384
return new FastBuffer();
386385
}
387386

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

403-
view = TypedArrayPrototypeSlice(view, offset, end);
390+
if (offset !== undefined) {
391+
validateInteger(offset, 'offset', 0);
392+
if (offset >= viewLength) return new FastBuffer();
393+
start = offset;
404394
}
405395

396+
if (length !== undefined) {
397+
validateInteger(length, 'length', 0);
398+
// The old code used TypedArrayPrototypeSlice which clamps internally.
399+
end = MathMin(start + length, viewLength);
400+
}
401+
402+
if (end <= start) return new FastBuffer();
403+
404+
const viewByteLength = TypedArrayPrototypeGetByteLength(view);
405+
const elementSize = viewByteLength / viewLength;
406+
const srcByteOffset = TypedArrayPrototypeGetByteOffset(view) +
407+
start * elementSize;
408+
const srcByteLength = (end - start) * elementSize;
409+
406410
return fromArrayLike(new Uint8Array(
407411
TypedArrayPrototypeGetBuffer(view),
408-
TypedArrayPrototypeGetByteOffset(view),
409-
TypedArrayPrototypeGetByteLength(view)));
412+
srcByteOffset,
413+
srcByteLength));
410414
};
411415

412416
// Identical to the built-in %TypedArray%.of(), but avoids using the deprecated
@@ -563,14 +567,15 @@ function fromArrayBuffer(obj, byteOffset, length) {
563567
}
564568

565569
function fromArrayLike(obj) {
566-
if (obj.length <= 0)
570+
const { length } = obj;
571+
if (length <= 0)
567572
return new FastBuffer();
568-
if (obj.length < (Buffer.poolSize >>> 1)) {
569-
if (obj.length > (poolSize - poolOffset))
573+
if (length < (Buffer.poolSize >>> 1)) {
574+
if (length > (poolSize - poolOffset))
570575
createPool();
571-
const b = new FastBuffer(allocPool, poolOffset, obj.length);
576+
const b = new FastBuffer(allocPool, poolOffset, length);
572577
TypedArrayPrototypeSet(b, obj, 0);
573-
poolOffset += obj.length;
578+
poolOffset += length;
574579
alignPool();
575580
return b;
576581
}
@@ -744,11 +749,7 @@ const encodingOps = {
744749
write: asciiWrite,
745750
slice: asciiSlice,
746751
indexOf: (buf, val, byteOffset, dir) =>
747-
indexOfBuffer(buf,
748-
fromStringFast(val, encodingOps.ascii),
749-
byteOffset,
750-
encodingsMap.ascii,
751-
dir),
752+
indexOfString(buf, val, byteOffset, encodingsMap.ascii, dir),
752753
},
753754
base64: {
754755
encoding: 'base64',
@@ -909,17 +910,17 @@ Buffer.prototype.toString = function toString(encoding, start, end) {
909910
return utf8Slice(this, 0, this.length);
910911
}
911912

912-
const len = this.length;
913+
const bufferLength = TypedArrayPrototypeGetLength(this);
913914

914915
if (start <= 0)
915916
start = 0;
916-
else if (start >= len)
917+
else if (start >= bufferLength)
917918
return '';
918919
else
919920
start = MathTrunc(start) || 0;
920921

921-
if (end === undefined || end > len)
922-
end = len;
922+
if (end === undefined || end > bufferLength)
923+
end = bufferLength;
923924
else
924925
end = MathTrunc(end) || 0;
925926

@@ -1130,7 +1131,9 @@ function _fill(buf, value, offset, end, encoding) {
11301131
value = 0;
11311132
} else if (value.length === 1) {
11321133
// Fast path: If `value` fits into a single byte, use that numeric value.
1133-
if (normalizedEncoding === 'utf8') {
1134+
// ASCII shares this branch with utf8 since code < 128 covers the full
1135+
// ASCII range; anything outside falls through to C++ bindingFill.
1136+
if (normalizedEncoding === 'utf8' || normalizedEncoding === 'ascii') {
11341137
const code = StringPrototypeCharCodeAt(value, 0);
11351138
if (code < 128) {
11361139
value = code;
@@ -1180,29 +1183,30 @@ function _fill(buf, value, offset, end, encoding) {
11801183
}
11811184

11821185
Buffer.prototype.write = function write(string, offset, length, encoding) {
1186+
const bufferLength = TypedArrayPrototypeGetLength(this);
11831187
// Buffer#write(string);
11841188
if (offset === undefined) {
1185-
return utf8Write(this, string, 0, this.length);
1189+
return utf8Write(this, string, 0, bufferLength);
11861190
}
11871191
// Buffer#write(string, encoding)
11881192
if (length === undefined && typeof offset === 'string') {
11891193
encoding = offset;
1190-
length = this.length;
1194+
length = bufferLength;
11911195
offset = 0;
11921196

11931197
// Buffer#write(string, offset[, length][, encoding])
11941198
} else {
1195-
validateOffset(offset, 'offset', 0, this.length);
1199+
validateOffset(offset, 'offset', 0, bufferLength);
11961200

1197-
const remaining = this.length - offset;
1201+
const remaining = bufferLength - offset;
11981202

11991203
if (length === undefined) {
12001204
length = remaining;
12011205
} else if (typeof length === 'string') {
12021206
encoding = length;
12031207
length = remaining;
12041208
} else {
1205-
validateOffset(length, 'length', 0, this.length);
1209+
validateOffset(length, 'length', 0, bufferLength);
12061210
if (length > remaining)
12071211
length = remaining;
12081212
}
@@ -1220,9 +1224,10 @@ Buffer.prototype.write = function write(string, offset, length, encoding) {
12201224
};
12211225

12221226
Buffer.prototype.toJSON = function toJSON() {
1223-
if (this.length > 0) {
1224-
const data = new Array(this.length);
1225-
for (let i = 0; i < this.length; ++i)
1227+
const bufferLength = TypedArrayPrototypeGetLength(this);
1228+
if (bufferLength > 0) {
1229+
const data = new Array(bufferLength);
1230+
for (let i = 0; i < bufferLength; ++i)
12261231
data[i] = this[i];
12271232
return { type: 'Buffer', data };
12281233
}
@@ -1247,7 +1252,7 @@ function adjustOffset(offset, length) {
12471252
}
12481253

12491254
Buffer.prototype.subarray = function subarray(start, end) {
1250-
const srcLength = this.length;
1255+
const srcLength = TypedArrayPrototypeGetLength(this);
12511256
start = adjustOffset(start, srcLength);
12521257
end = end !== undefined ? adjustOffset(end, srcLength) : srcLength;
12531258
const newLength = end > start ? end - start : 0;
@@ -1265,45 +1270,52 @@ function swap(b, n, m) {
12651270
}
12661271

12671272
Buffer.prototype.swap16 = function swap16() {
1268-
// For Buffer.length < 128, it's generally faster to
1273+
// Ref: https://github.com/nodejs/node/pull/61871#discussion_r2889557696
1274+
// For Buffer.length <= 32, it's generally faster to
12691275
// do the swap in javascript. For larger buffers,
12701276
// dropping down to the native code is faster.
1271-
const len = this.length;
1277+
const len = TypedArrayPrototypeGetLength(this);
12721278
if (len % 2 !== 0)
12731279
throw new ERR_INVALID_BUFFER_SIZE('16-bits');
1274-
if (len < 128) {
1280+
if (len <= 32) {
12751281
for (let i = 0; i < len; i += 2)
12761282
swap(this, i, i + 1);
12771283
return this;
12781284
}
1279-
return _swap16(this);
1285+
_swap16(this);
1286+
return this;
12801287
};
12811288

12821289
Buffer.prototype.swap32 = function swap32() {
1283-
// For Buffer.length < 192, it's generally faster to
1290+
// Ref: https://github.com/nodejs/node/pull/61871#discussion_r2889557696
1291+
// For Buffer.length <= 32, it's generally faster to
12841292
// do the swap in javascript. For larger buffers,
12851293
// dropping down to the native code is faster.
1286-
const len = this.length;
1294+
const len = TypedArrayPrototypeGetLength(this);
12871295
if (len % 4 !== 0)
12881296
throw new ERR_INVALID_BUFFER_SIZE('32-bits');
1289-
if (len < 192) {
1297+
if (len <= 32) {
12901298
for (let i = 0; i < len; i += 4) {
12911299
swap(this, i, i + 3);
12921300
swap(this, i + 1, i + 2);
12931301
}
12941302
return this;
12951303
}
1296-
return _swap32(this);
1304+
_swap32(this);
1305+
return this;
12971306
};
12981307

12991308
Buffer.prototype.swap64 = function swap64() {
1300-
// For Buffer.length < 192, it's generally faster to
1309+
// Ref: https://github.com/nodejs/node/pull/61871#discussion_r2889557696
1310+
// For Buffer.length < 48, it's generally faster to
13011311
// do the swap in javascript. For larger buffers,
13021312
// dropping down to the native code is faster.
1303-
const len = this.length;
1313+
// Threshold differs from swap16/swap32 (<=32) because swap64's
1314+
// crossover is between 40 and 48 (native wins at 48, loses at 40).
1315+
const len = TypedArrayPrototypeGetLength(this);
13041316
if (len % 8 !== 0)
13051317
throw new ERR_INVALID_BUFFER_SIZE('64-bits');
1306-
if (len < 192) {
1318+
if (len < 48) {
13071319
for (let i = 0; i < len; i += 8) {
13081320
swap(this, i, i + 7);
13091321
swap(this, i + 1, i + 6);
@@ -1312,7 +1324,8 @@ Buffer.prototype.swap64 = function swap64() {
13121324
}
13131325
return this;
13141326
}
1315-
return _swap64(this);
1327+
_swap64(this);
1328+
return this;
13161329
};
13171330

13181331
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.