Skip to content

Commit 4a86803

Browse files
mscdexaddaleax
authored andcommitted
buffer: optimize from() and byteLength()
PR-URL: #12361 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 46f2026 commit 4a86803

File tree

5 files changed

+129
-86
lines changed

5 files changed

+129
-86
lines changed

benchmark/buffers/buffer-bytelength.js

+20-14
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
var common = require('../common');
33

44
var bench = common.createBenchmark(main, {
5-
encoding: ['utf8', 'base64'],
5+
encoding: ['utf8', 'base64', 'buffer'],
66
len: [1, 2, 4, 16, 64, 256], // x16
77
n: [5e6]
88
});
@@ -21,21 +21,27 @@ function main(conf) {
2121
var encoding = conf.encoding;
2222

2323
var strings = [];
24-
for (var string of chars) {
25-
// Strings must be built differently, depending on encoding
26-
var data = buildString(string, len);
27-
if (encoding === 'utf8') {
28-
strings.push(data);
29-
} else if (encoding === 'base64') {
30-
// Base64 strings will be much longer than their UTF8 counterparts
31-
strings.push(Buffer.from(data, 'utf8').toString('base64'));
24+
var results;
25+
if (encoding === 'buffer') {
26+
strings = [ Buffer.alloc(len * 16, 'a') ];
27+
results = [ len * 16 ];
28+
} else {
29+
for (var string of chars) {
30+
// Strings must be built differently, depending on encoding
31+
var data = buildString(string, len);
32+
if (encoding === 'utf8') {
33+
strings.push(data);
34+
} else if (encoding === 'base64') {
35+
// Base64 strings will be much longer than their UTF8 counterparts
36+
strings.push(Buffer.from(data, 'utf8').toString('base64'));
37+
}
3238
}
33-
}
3439

35-
// Check the result to ensure it is *properly* optimized
36-
var results = strings.map(function(val) {
37-
return Buffer.byteLength(val, encoding);
38-
});
40+
// Check the result to ensure it is *properly* optimized
41+
results = strings.map(function(val) {
42+
return Buffer.byteLength(val, encoding);
43+
});
44+
}
3945

4046
bench.start();
4147
for (var i = 0; i < n; i++) {

benchmark/buffers/buffer-from.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ const bench = common.createBenchmark(main, {
1010
'buffer',
1111
'uint8array',
1212
'string',
13+
'string-utf8',
1314
'string-base64',
1415
'object'
1516
],
1617
len: [10, 2048],
17-
n: [1024]
18+
n: [2048]
1819
});
1920

2021
function main(conf) {
@@ -75,6 +76,13 @@ function main(conf) {
7576
}
7677
bench.end(n);
7778
break;
79+
case 'string-utf8':
80+
bench.start();
81+
for (i = 0; i < n * 1024; i++) {
82+
Buffer.from(str, 'utf8');
83+
}
84+
bench.end(n);
85+
break;
7886
case 'string-base64':
7987
bench.start();
8088
for (i = 0; i < n * 1024; i++) {

lib/buffer.js

+90-66
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@
2323

2424
const binding = process.binding('buffer');
2525
const { compare: compare_, compareOffset } = binding;
26-
const { isArrayBuffer, isSharedArrayBuffer, isUint8Array } =
27-
process.binding('util');
26+
const { isAnyArrayBuffer, isUint8Array } = process.binding('util');
2827
const bindingObj = {};
2928
const internalUtil = require('internal/util');
3029

@@ -116,16 +115,19 @@ function Buffer(arg, encodingOrOffset, length) {
116115
* Buffer.from(arrayBuffer[, byteOffset[, length]])
117116
**/
118117
Buffer.from = function(value, encodingOrOffset, length) {
119-
if (typeof value === 'number')
120-
throw new TypeError('"value" argument must not be a number');
118+
if (typeof value === 'string')
119+
return fromString(value, encodingOrOffset);
121120

122-
if (isArrayBuffer(value) || isSharedArrayBuffer(value))
121+
if (isAnyArrayBuffer(value))
123122
return fromArrayBuffer(value, encodingOrOffset, length);
124123

125-
if (typeof value === 'string')
126-
return fromString(value, encodingOrOffset);
124+
var b = fromObject(value);
125+
if (b)
126+
return b;
127127

128-
return fromObject(value);
128+
if (typeof value === 'number')
129+
throw new TypeError('"value" argument must not be a number');
130+
throw new TypeError(kFromErrorMsg);
129131
};
130132

131133
Object.setPrototypeOf(Buffer, Uint8Array);
@@ -218,24 +220,27 @@ function allocate(size) {
218220

219221

220222
function fromString(string, encoding) {
221-
if (typeof encoding !== 'string' || encoding === '')
223+
var length;
224+
if (typeof encoding !== 'string' || encoding.length === 0) {
222225
encoding = 'utf8';
223-
224-
if (!Buffer.isEncoding(encoding))
225-
throw new TypeError('"encoding" must be a valid string encoding');
226-
227-
if (string.length === 0)
228-
return new FastBuffer();
229-
230-
var length = byteLength(string, encoding);
226+
if (string.length === 0)
227+
return new FastBuffer();
228+
length = binding.byteLengthUtf8(string);
229+
} else {
230+
length = byteLength(string, encoding, true);
231+
if (length === -1)
232+
throw new TypeError('"encoding" must be a valid string encoding');
233+
if (string.length === 0)
234+
return new FastBuffer();
235+
}
231236

232237
if (length >= (Buffer.poolSize >>> 1))
233238
return binding.createFromString(string, encoding);
234239

235240
if (length > (poolSize - poolOffset))
236241
createPool();
237242
var b = new FastBuffer(allocPool, poolOffset, length);
238-
var actual = b.write(string, encoding);
243+
const actual = b.write(string, encoding);
239244
if (actual !== length) {
240245
// byteLength() may overestimate. That's a rare case, though.
241246
b = new FastBuffer(allocPool, poolOffset, actual);
@@ -255,8 +260,14 @@ function fromArrayLike(obj) {
255260

256261
function fromArrayBuffer(obj, byteOffset, length) {
257262
// convert byteOffset to integer
258-
byteOffset = +byteOffset;
259-
byteOffset = byteOffset ? Math.trunc(byteOffset) : 0;
263+
if (byteOffset === undefined) {
264+
byteOffset = 0;
265+
} else {
266+
byteOffset = +byteOffset;
267+
// check for NaN
268+
if (byteOffset !== byteOffset)
269+
byteOffset = 0;
270+
}
260271

261272
const maxLength = obj.byteLength - byteOffset;
262273

@@ -268,11 +279,17 @@ function fromArrayBuffer(obj, byteOffset, length) {
268279
} else {
269280
// convert length to non-negative integer
270281
length = +length;
271-
length = length ? Math.trunc(length) : 0;
272-
length = length <= 0 ? 0 : Math.min(length, Number.MAX_SAFE_INTEGER);
273-
274-
if (length > maxLength)
275-
throw new RangeError("'length' is out of bounds");
282+
// Check for NaN
283+
if (length !== length) {
284+
length = 0;
285+
} else if (length > 0) {
286+
length = (length < Number.MAX_SAFE_INTEGER ?
287+
length : Number.MAX_SAFE_INTEGER);
288+
if (length > maxLength)
289+
throw new RangeError("'length' is out of bounds");
290+
} else {
291+
length = 0;
292+
}
276293
}
277294

278295
return new FastBuffer(obj, byteOffset, length);
@@ -289,9 +306,8 @@ function fromObject(obj) {
289306
return b;
290307
}
291308

292-
if (obj) {
293-
if (obj.length !== undefined || isArrayBuffer(obj.buffer) ||
294-
isSharedArrayBuffer(obj.buffer)) {
309+
if (obj != undefined) {
310+
if (obj.length !== undefined || isAnyArrayBuffer(obj.buffer)) {
295311
if (typeof obj.length !== 'number' || obj.length !== obj.length) {
296312
return new FastBuffer();
297313
}
@@ -302,8 +318,6 @@ function fromObject(obj) {
302318
return fromArrayLike(obj.data);
303319
}
304320
}
305-
306-
throw new TypeError(kFromErrorMsg);
307321
}
308322

309323

@@ -388,53 +402,63 @@ function base64ByteLength(str, bytes) {
388402

389403
function byteLength(string, encoding) {
390404
if (typeof string !== 'string') {
391-
if (ArrayBuffer.isView(string) || isArrayBuffer(string) ||
392-
isSharedArrayBuffer(string)) {
405+
if (ArrayBuffer.isView(string) || isAnyArrayBuffer(string)) {
393406
return string.byteLength;
394407
}
395408

396409
throw new TypeError('"string" must be a string, Buffer, or ArrayBuffer');
397410
}
398411

399-
var len = string.length;
400-
if (len === 0)
412+
const len = string.length;
413+
const mustMatch = (arguments.length > 2 && arguments[2] === true);
414+
if (!mustMatch && len === 0)
401415
return 0;
402416

403-
// Use a for loop to avoid recursion
404-
var loweredCase = false;
405-
for (;;) {
406-
switch (encoding) {
407-
case 'ascii':
408-
case 'latin1':
409-
case 'binary':
410-
return len;
411-
412-
case 'utf8':
413-
case 'utf-8':
414-
case undefined:
415-
return binding.byteLengthUtf8(string);
416-
417-
case 'ucs2':
418-
case 'ucs-2':
419-
case 'utf16le':
420-
case 'utf-16le':
417+
if (!encoding)
418+
return (mustMatch ? -1 : binding.byteLengthUtf8(string));
419+
420+
encoding += '';
421+
switch (encoding.length) {
422+
case 4:
423+
if (encoding === 'utf8') return binding.byteLengthUtf8(string);
424+
if (encoding === 'ucs2') return len * 2;
425+
encoding = encoding.toLowerCase();
426+
if (encoding === 'utf8') return binding.byteLengthUtf8(string);
427+
if (encoding === 'ucs2') return len * 2;
428+
break;
429+
case 5:
430+
if (encoding === 'utf-8') return binding.byteLengthUtf8(string);
431+
if (encoding === 'ascii') return len;
432+
if (encoding === 'ucs-2') return len * 2;
433+
encoding = encoding.toLowerCase();
434+
if (encoding === 'utf-8') return binding.byteLengthUtf8(string);
435+
if (encoding === 'ascii') return len;
436+
if (encoding === 'ucs-2') return len * 2;
437+
break;
438+
case 7:
439+
if (encoding === 'utf16le' || encoding.toLowerCase() === 'utf16le')
421440
return len * 2;
422-
423-
case 'hex':
441+
break;
442+
case 8:
443+
if (encoding === 'utf-16le' || encoding.toLowerCase() === 'utf-16le')
444+
return len * 2;
445+
break;
446+
case 6:
447+
if (encoding === 'latin1' || encoding === 'binary') return len;
448+
if (encoding === 'base64') return base64ByteLength(string, len);
449+
encoding = encoding.toLowerCase();
450+
if (encoding === 'latin1' || encoding === 'binary') return len;
451+
if (encoding === 'base64') return base64ByteLength(string, len);
452+
break;
453+
case 3:
454+
if (encoding === 'hex' || encoding.toLowerCase() === 'hex')
424455
return len >>> 1;
425-
426-
case 'base64':
427-
return base64ByteLength(string, len);
428-
429-
default:
430-
// The C++ binding defaulted to UTF8, we should too.
431-
if (loweredCase)
432-
return binding.byteLengthUtf8(string);
433-
434-
encoding = ('' + encoding).toLowerCase();
435-
loweredCase = true;
436-
}
456+
break;
437457
}
458+
if (mustMatch)
459+
throw new TypeError('Unknown encoding: ' + encoding);
460+
else
461+
return binding.byteLengthUtf8(string);
438462
}
439463

440464
Buffer.byteLength = byteLength;

lib/util.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ function formatValue(ctx, value, recurseTimes) {
452452
// Fast path for ArrayBuffer and SharedArrayBuffer.
453453
// Can't do the same for DataView because it has a non-primitive
454454
// .buffer property that we need to recurse for.
455-
if (binding.isArrayBuffer(value) || binding.isSharedArrayBuffer(value)) {
455+
if (binding.isAnyArrayBuffer(value)) {
456456
return `${constructor.name}` +
457457
` { byteLength: ${formatNumber(ctx, value.byteLength)} }`;
458458
}
@@ -494,8 +494,7 @@ function formatValue(ctx, value, recurseTimes) {
494494
keys.unshift('size');
495495
empty = value.size === 0;
496496
formatter = formatMap;
497-
} else if (binding.isArrayBuffer(value) ||
498-
binding.isSharedArrayBuffer(value)) {
497+
} else if (binding.isAnyArrayBuffer(value)) {
499498
braces = ['{', '}'];
500499
keys.unshift('byteLength');
501500
visibleKeys.byteLength = true;

src/node_util.cc

+8-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ using v8::Value;
2020

2121

2222
#define VALUE_METHOD_MAP(V) \
23-
V(isArrayBuffer, IsArrayBuffer) \
2423
V(isDataView, IsDataView) \
2524
V(isDate, IsDate) \
2625
V(isExternal, IsExternal) \
@@ -30,7 +29,6 @@ using v8::Value;
3029
V(isRegExp, IsRegExp) \
3130
V(isSet, IsSet) \
3231
V(isSetIterator, IsSetIterator) \
33-
V(isSharedArrayBuffer, IsSharedArrayBuffer) \
3432
V(isTypedArray, IsTypedArray) \
3533
V(isUint8Array, IsUint8Array)
3634

@@ -44,6 +42,12 @@ using v8::Value;
4442
VALUE_METHOD_MAP(V)
4543
#undef V
4644

45+
static void IsAnyArrayBuffer(const FunctionCallbackInfo<Value>& args) {
46+
CHECK_EQ(1, args.Length());
47+
args.GetReturnValue().Set(
48+
args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer());
49+
}
50+
4751
static void GetPromiseDetails(const FunctionCallbackInfo<Value>& args) {
4852
// Return undefined if it's not a Promise.
4953
if (!args[0]->IsPromise())
@@ -151,6 +155,8 @@ void Initialize(Local<Object> target,
151155
VALUE_METHOD_MAP(V)
152156
#undef V
153157

158+
env->SetMethod(target, "isAnyArrayBuffer", IsAnyArrayBuffer);
159+
154160
#define V(name, _) \
155161
target->Set(context, \
156162
FIXED_ONE_BYTE_STRING(env->isolate(), #name), \

0 commit comments

Comments
 (0)