mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
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: https://github.com/nodejs/node/pull/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>
This commit is contained in:
1 parent
7d1f1b4483
commit
9e0dc8b472
10 files changed
+280
-70
No files matched your search
@@ -4,7 +4,7 @@ const common = require('../common');
|
||||
const bench = common.createBenchmark(main, {
|
||||
type: ['one_byte', 'two_bytes', 'three_bytes',
|
||||
'four_bytes', 'latin1'],
|
||||
encoding: ['utf8', 'base64'],
|
||||
encoding: ['utf8', 'base64', 'latin1', 'hex'],
|
||||
repeat: [1, 2, 16, 256], // x16
|
||||
n: [4e6],
|
||||
});
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common.js');
|
||||
|
||||
const bench = common.createBenchmark(main, {
|
||||
type: ['Uint8Array', 'Uint16Array', 'Uint32Array', 'Float64Array'],
|
||||
len: [64, 256, 2048],
|
||||
partial: ['none', 'offset', 'offset-length'],
|
||||
n: [6e5],
|
||||
});
|
||||
|
||||
function main({ n, len, type, partial }) {
|
||||
const TypedArrayCtor = globalThis[type];
|
||||
const src = new TypedArrayCtor(len);
|
||||
for (let i = 0; i < len; i++) src[i] = i;
|
||||
|
||||
let offset;
|
||||
let length;
|
||||
if (partial === 'offset') {
|
||||
offset = len >>> 2;
|
||||
} else if (partial === 'offset-length') {
|
||||
offset = len >>> 2;
|
||||
length = len >>> 1;
|
||||
}
|
||||
|
||||
bench.start();
|
||||
for (let i = 0; i < n; i++) {
|
||||
Buffer.copyBytesFrom(src, offset, length);
|
||||
}
|
||||
bench.end(n);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ const bench = common.createBenchmark(main, {
|
||||
'fill("t")',
|
||||
'fill("test")',
|
||||
'fill("t", "utf8")',
|
||||
'fill("t", "ascii")',
|
||||
'fill("t", 0, "utf8")',
|
||||
'fill("t", 0)',
|
||||
'fill(Buffer.alloc(1), 0)',
|
||||
|
||||
@@ -19,7 +19,7 @@ const searchStrings = [
|
||||
|
||||
const bench = common.createBenchmark(main, {
|
||||
search: searchStrings,
|
||||
encoding: ['undefined', 'utf8', 'ucs2', 'latin1'],
|
||||
encoding: ['undefined', 'utf8', 'ascii', 'latin1', 'ucs2'],
|
||||
type: ['buffer', 'string'],
|
||||
n: [5e4],
|
||||
}, {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
const common = require('../common.js');
|
||||
|
||||
const bench = common.createBenchmark(main, {
|
||||
encoding: ['', 'utf8', 'ascii', 'latin1', 'hex', 'UCS-2'],
|
||||
encoding: ['', 'utf8', 'ascii', 'latin1', 'hex', 'base64', 'base64url', 'UCS-2'],
|
||||
args: [0, 1, 3],
|
||||
len: [1, 64, 1024],
|
||||
n: [1e6],
|
||||
|
||||
+67
-54
@@ -50,7 +50,6 @@ const {
|
||||
TypedArrayPrototypeGetByteOffset,
|
||||
TypedArrayPrototypeGetLength,
|
||||
TypedArrayPrototypeSet,
|
||||
TypedArrayPrototypeSlice,
|
||||
TypedArrayPrototypeSubarray,
|
||||
Uint8Array,
|
||||
} = primordials;
|
||||
@@ -383,28 +382,33 @@ Buffer.copyBytesFrom = function copyBytesFrom(view, offset, length) {
|
||||
return new FastBuffer();
|
||||
}
|
||||
|
||||
if (offset !== undefined || length !== undefined) {
|
||||
if (offset !== undefined) {
|
||||
validateInteger(offset, 'offset', 0);
|
||||
if (offset >= viewLength) return new FastBuffer();
|
||||
} else {
|
||||
offset = 0;
|
||||
}
|
||||
let end;
|
||||
if (length !== undefined) {
|
||||
validateInteger(length, 'length', 0);
|
||||
end = offset + length;
|
||||
} else {
|
||||
end = viewLength;
|
||||
}
|
||||
let start = 0;
|
||||
let end = viewLength;
|
||||
|
||||
view = TypedArrayPrototypeSlice(view, offset, end);
|
||||
if (offset !== undefined) {
|
||||
validateInteger(offset, 'offset', 0);
|
||||
if (offset >= viewLength) return new FastBuffer();
|
||||
start = offset;
|
||||
}
|
||||
|
||||
if (length !== undefined) {
|
||||
validateInteger(length, 'length', 0);
|
||||
// The old code used TypedArrayPrototypeSlice which clamps internally.
|
||||
end = MathMin(start + length, viewLength);
|
||||
}
|
||||
|
||||
if (end <= start) return new FastBuffer();
|
||||
|
||||
const viewByteLength = TypedArrayPrototypeGetByteLength(view);
|
||||
const elementSize = viewByteLength / viewLength;
|
||||
const srcByteOffset = TypedArrayPrototypeGetByteOffset(view) +
|
||||
start * elementSize;
|
||||
const srcByteLength = (end - start) * elementSize;
|
||||
|
||||
return fromArrayLike(new Uint8Array(
|
||||
TypedArrayPrototypeGetBuffer(view),
|
||||
TypedArrayPrototypeGetByteOffset(view),
|
||||
TypedArrayPrototypeGetByteLength(view)));
|
||||
srcByteOffset,
|
||||
srcByteLength));
|
||||
};
|
||||
|
||||
// Identical to the built-in %TypedArray%.of(), but avoids using the deprecated
|
||||
@@ -551,14 +555,15 @@ function fromArrayBuffer(obj, byteOffset, length) {
|
||||
}
|
||||
|
||||
function fromArrayLike(obj) {
|
||||
if (obj.length <= 0)
|
||||
const { length } = obj;
|
||||
if (length <= 0)
|
||||
return new FastBuffer();
|
||||
if (obj.length < (Buffer.poolSize >>> 1)) {
|
||||
if (obj.length > (poolSize - poolOffset))
|
||||
if (length < (Buffer.poolSize >>> 1)) {
|
||||
if (length > (poolSize - poolOffset))
|
||||
createPool();
|
||||
const b = new FastBuffer(allocPool, poolOffset, obj.length);
|
||||
const b = new FastBuffer(allocPool, poolOffset, length);
|
||||
TypedArrayPrototypeSet(b, obj, 0);
|
||||
poolOffset += obj.length;
|
||||
poolOffset += length;
|
||||
alignPool();
|
||||
return b;
|
||||
}
|
||||
@@ -732,11 +737,7 @@ const encodingOps = {
|
||||
write: asciiWrite,
|
||||
slice: asciiSlice,
|
||||
indexOf: (buf, val, byteOffset, dir) =>
|
||||
indexOfBuffer(buf,
|
||||
fromStringFast(val, encodingOps.ascii),
|
||||
byteOffset,
|
||||
encodingsMap.ascii,
|
||||
dir),
|
||||
indexOfString(buf, val, byteOffset, encodingsMap.ascii, dir),
|
||||
},
|
||||
base64: {
|
||||
encoding: 'base64',
|
||||
@@ -897,17 +898,17 @@ Buffer.prototype.toString = function toString(encoding, start, end) {
|
||||
return utf8Slice(this, 0, this.length);
|
||||
}
|
||||
|
||||
const len = this.length;
|
||||
const bufferLength = TypedArrayPrototypeGetLength(this);
|
||||
|
||||
if (start <= 0)
|
||||
start = 0;
|
||||
else if (start >= len)
|
||||
else if (start >= bufferLength)
|
||||
return '';
|
||||
else
|
||||
start = MathTrunc(start) || 0;
|
||||
|
||||
if (end === undefined || end > len)
|
||||
end = len;
|
||||
if (end === undefined || end > bufferLength)
|
||||
end = bufferLength;
|
||||
else
|
||||
end = MathTrunc(end) || 0;
|
||||
|
||||
@@ -1118,7 +1119,9 @@ function _fill(buf, value, offset, end, encoding) {
|
||||
value = 0;
|
||||
} else if (value.length === 1) {
|
||||
// Fast path: If `value` fits into a single byte, use that numeric value.
|
||||
if (normalizedEncoding === 'utf8') {
|
||||
// ASCII shares this branch with utf8 since code < 128 covers the full
|
||||
// ASCII range; anything outside falls through to C++ bindingFill.
|
||||
if (normalizedEncoding === 'utf8' || normalizedEncoding === 'ascii') {
|
||||
const code = StringPrototypeCharCodeAt(value, 0);
|
||||
if (code < 128) {
|
||||
value = code;
|
||||
@@ -1168,21 +1171,22 @@ function _fill(buf, value, offset, end, encoding) {
|
||||
}
|
||||
|
||||
Buffer.prototype.write = function write(string, offset, length, encoding) {
|
||||
const bufferLength = TypedArrayPrototypeGetLength(this);
|
||||
// Buffer#write(string);
|
||||
if (offset === undefined) {
|
||||
return utf8Write(this, string, 0, this.length);
|
||||
return utf8Write(this, string, 0, bufferLength);
|
||||
}
|
||||
// Buffer#write(string, encoding)
|
||||
if (length === undefined && typeof offset === 'string') {
|
||||
encoding = offset;
|
||||
length = this.length;
|
||||
length = bufferLength;
|
||||
offset = 0;
|
||||
|
||||
// Buffer#write(string, offset[, length][, encoding])
|
||||
} else {
|
||||
validateOffset(offset, 'offset', 0, this.length);
|
||||
validateOffset(offset, 'offset', 0, bufferLength);
|
||||
|
||||
const remaining = this.length - offset;
|
||||
const remaining = bufferLength - offset;
|
||||
|
||||
if (length === undefined) {
|
||||
length = remaining;
|
||||
@@ -1190,7 +1194,7 @@ Buffer.prototype.write = function write(string, offset, length, encoding) {
|
||||
encoding = length;
|
||||
length = remaining;
|
||||
} else {
|
||||
validateOffset(length, 'length', 0, this.length);
|
||||
validateOffset(length, 'length', 0, bufferLength);
|
||||
if (length > remaining)
|
||||
length = remaining;
|
||||
}
|
||||
@@ -1208,9 +1212,10 @@ Buffer.prototype.write = function write(string, offset, length, encoding) {
|
||||
};
|
||||
|
||||
Buffer.prototype.toJSON = function toJSON() {
|
||||
if (this.length > 0) {
|
||||
const data = new Array(this.length);
|
||||
for (let i = 0; i < this.length; ++i)
|
||||
const bufferLength = TypedArrayPrototypeGetLength(this);
|
||||
if (bufferLength > 0) {
|
||||
const data = new Array(bufferLength);
|
||||
for (let i = 0; i < bufferLength; ++i)
|
||||
data[i] = this[i];
|
||||
return { type: 'Buffer', data };
|
||||
}
|
||||
@@ -1235,7 +1240,7 @@ function adjustOffset(offset, length) {
|
||||
}
|
||||
|
||||
Buffer.prototype.subarray = function subarray(start, end) {
|
||||
const srcLength = this.length;
|
||||
const srcLength = TypedArrayPrototypeGetLength(this);
|
||||
start = adjustOffset(start, srcLength);
|
||||
end = end !== undefined ? adjustOffset(end, srcLength) : srcLength;
|
||||
const newLength = end > start ? end - start : 0;
|
||||
@@ -1253,45 +1258,52 @@ function swap(b, n, m) {
|
||||
}
|
||||
|
||||
Buffer.prototype.swap16 = function swap16() {
|
||||
// For Buffer.length < 128, it's generally faster to
|
||||
// Ref: https://github.com/nodejs/node/pull/61871#discussion_r2889557696
|
||||
// For Buffer.length <= 32, it's generally faster to
|
||||
// do the swap in javascript. For larger buffers,
|
||||
// dropping down to the native code is faster.
|
||||
const len = this.length;
|
||||
const len = TypedArrayPrototypeGetLength(this);
|
||||
if (len % 2 !== 0)
|
||||
throw new ERR_INVALID_BUFFER_SIZE('16-bits');
|
||||
if (len < 128) {
|
||||
if (len <= 32) {
|
||||
for (let i = 0; i < len; i += 2)
|
||||
swap(this, i, i + 1);
|
||||
return this;
|
||||
}
|
||||
return _swap16(this);
|
||||
_swap16(this);
|
||||
return this;
|
||||
};
|
||||
|
||||
Buffer.prototype.swap32 = function swap32() {
|
||||
// For Buffer.length < 192, it's generally faster to
|
||||
// Ref: https://github.com/nodejs/node/pull/61871#discussion_r2889557696
|
||||
// For Buffer.length <= 32, it's generally faster to
|
||||
// do the swap in javascript. For larger buffers,
|
||||
// dropping down to the native code is faster.
|
||||
const len = this.length;
|
||||
const len = TypedArrayPrototypeGetLength(this);
|
||||
if (len % 4 !== 0)
|
||||
throw new ERR_INVALID_BUFFER_SIZE('32-bits');
|
||||
if (len < 192) {
|
||||
if (len <= 32) {
|
||||
for (let i = 0; i < len; i += 4) {
|
||||
swap(this, i, i + 3);
|
||||
swap(this, i + 1, i + 2);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
return _swap32(this);
|
||||
_swap32(this);
|
||||
return this;
|
||||
};
|
||||
|
||||
Buffer.prototype.swap64 = function swap64() {
|
||||
// For Buffer.length < 192, it's generally faster to
|
||||
// Ref: https://github.com/nodejs/node/pull/61871#discussion_r2889557696
|
||||
// For Buffer.length < 48, it's generally faster to
|
||||
// do the swap in javascript. For larger buffers,
|
||||
// dropping down to the native code is faster.
|
||||
const len = this.length;
|
||||
// Threshold differs from swap16/swap32 (<=32) because swap64's
|
||||
// crossover is between 40 and 48 (native wins at 48, loses at 40).
|
||||
const len = TypedArrayPrototypeGetLength(this);
|
||||
if (len % 8 !== 0)
|
||||
throw new ERR_INVALID_BUFFER_SIZE('64-bits');
|
||||
if (len < 192) {
|
||||
if (len < 48) {
|
||||
for (let i = 0; i < len; i += 8) {
|
||||
swap(this, i, i + 7);
|
||||
swap(this, i + 1, i + 6);
|
||||
@@ -1300,7 +1312,8 @@ Buffer.prototype.swap64 = function swap64() {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
return _swap64(this);
|
||||
_swap64(this);
|
||||
return this;
|
||||
};
|
||||
|
||||
Buffer.prototype.toLocaleString = Buffer.prototype.toString;
|
||||
|
||||
+44
-13
@@ -1050,7 +1050,7 @@ void IndexOfString(const FunctionCallbackInfo<Value>& args) {
|
||||
needle_length,
|
||||
offset,
|
||||
is_forward);
|
||||
} else if (enc == LATIN1) {
|
||||
} else if (enc == ASCII || enc == LATIN1) {
|
||||
MaybeStackBuffer<uint8_t> needle_data(needle_length);
|
||||
StringBytes::Write(isolate,
|
||||
reinterpret_cast<char*>(needle_data.out()),
|
||||
@@ -1196,31 +1196,59 @@ int32_t FastIndexOfNumber(Local<Value>,
|
||||
static CFunction fast_index_of_number(CFunction::Make(FastIndexOfNumber));
|
||||
|
||||
void Swap16(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]);
|
||||
DCHECK(args[0]->IsArrayBufferView());
|
||||
SPREAD_BUFFER_ARG(args[0], ts_obj);
|
||||
CHECK(nbytes::SwapBytes16(ts_obj_data, ts_obj_length));
|
||||
args.GetReturnValue().Set(args[0]);
|
||||
}
|
||||
|
||||
void FastSwap16(Local<Value> receiver,
|
||||
Local<Value> buffer_obj,
|
||||
// NOLINTNEXTLINE(runtime/references)
|
||||
FastApiCallbackOptions& options) {
|
||||
TRACK_V8_FAST_API_CALL("buffer.swap16");
|
||||
HandleScope scope(options.isolate);
|
||||
SPREAD_BUFFER_ARG(buffer_obj, ts_obj);
|
||||
CHECK(nbytes::SwapBytes16(ts_obj_data, ts_obj_length));
|
||||
}
|
||||
|
||||
static CFunction fast_swap16(CFunction::Make(FastSwap16));
|
||||
|
||||
void Swap32(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]);
|
||||
DCHECK(args[0]->IsArrayBufferView());
|
||||
SPREAD_BUFFER_ARG(args[0], ts_obj);
|
||||
CHECK(nbytes::SwapBytes32(ts_obj_data, ts_obj_length));
|
||||
args.GetReturnValue().Set(args[0]);
|
||||
}
|
||||
|
||||
void FastSwap32(Local<Value> receiver,
|
||||
Local<Value> buffer_obj,
|
||||
// NOLINTNEXTLINE(runtime/references)
|
||||
FastApiCallbackOptions& options) {
|
||||
TRACK_V8_FAST_API_CALL("buffer.swap32");
|
||||
HandleScope scope(options.isolate);
|
||||
SPREAD_BUFFER_ARG(buffer_obj, ts_obj);
|
||||
CHECK(nbytes::SwapBytes32(ts_obj_data, ts_obj_length));
|
||||
}
|
||||
|
||||
static CFunction fast_swap32(CFunction::Make(FastSwap32));
|
||||
|
||||
void Swap64(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]);
|
||||
DCHECK(args[0]->IsArrayBufferView());
|
||||
SPREAD_BUFFER_ARG(args[0], ts_obj);
|
||||
CHECK(nbytes::SwapBytes64(ts_obj_data, ts_obj_length));
|
||||
args.GetReturnValue().Set(args[0]);
|
||||
}
|
||||
|
||||
void FastSwap64(Local<Value> receiver,
|
||||
Local<Value> buffer_obj,
|
||||
// NOLINTNEXTLINE(runtime/references)
|
||||
FastApiCallbackOptions& options) {
|
||||
TRACK_V8_FAST_API_CALL("buffer.swap64");
|
||||
HandleScope scope(options.isolate);
|
||||
SPREAD_BUFFER_ARG(buffer_obj, ts_obj);
|
||||
CHECK(nbytes::SwapBytes64(ts_obj_data, ts_obj_length));
|
||||
}
|
||||
|
||||
static CFunction fast_swap64(CFunction::Make(FastSwap64));
|
||||
|
||||
static void IsUtf8(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
CHECK_EQ(args.Length(), 1);
|
||||
@@ -1619,9 +1647,9 @@ void Initialize(Local<Object> target,
|
||||
SetMethodNoSideEffect(
|
||||
context, target, "createUnsafeArrayBuffer", CreateUnsafeArrayBuffer);
|
||||
|
||||
SetMethod(context, target, "swap16", Swap16);
|
||||
SetMethod(context, target, "swap32", Swap32);
|
||||
SetMethod(context, target, "swap64", Swap64);
|
||||
SetFastMethod(context, target, "swap16", Swap16, &fast_swap16);
|
||||
SetFastMethod(context, target, "swap32", Swap32, &fast_swap32);
|
||||
SetFastMethod(context, target, "swap64", Swap64, &fast_swap64);
|
||||
|
||||
SetMethodNoSideEffect(context, target, "isUtf8", IsUtf8);
|
||||
SetMethodNoSideEffect(context, target, "isAscii", IsAscii);
|
||||
@@ -1690,8 +1718,11 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
registry->Register(IndexOfString);
|
||||
|
||||
registry->Register(Swap16);
|
||||
registry->Register(fast_swap16);
|
||||
registry->Register(Swap32);
|
||||
registry->Register(fast_swap32);
|
||||
registry->Register(Swap64);
|
||||
registry->Register(fast_swap64);
|
||||
|
||||
registry->Register(IsUtf8);
|
||||
registry->Register(IsAscii);
|
||||
|
||||
@@ -140,5 +140,44 @@ assert.throws(() => {
|
||||
})
|
||||
);
|
||||
|
||||
// copyBytesFrom: length exceeds view (should clamp, not throw)
|
||||
{
|
||||
const u8 = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
const b = Buffer.copyBytesFrom(u8, 2, 100);
|
||||
assert.strictEqual(b.length, 3);
|
||||
assert.deepStrictEqual([...b], [3, 4, 5]);
|
||||
}
|
||||
|
||||
// copyBytesFrom: length 0 returns empty buffer
|
||||
{
|
||||
const u8 = new Uint8Array([1, 2, 3]);
|
||||
const b = Buffer.copyBytesFrom(u8, 1, 0);
|
||||
assert.strictEqual(b.length, 0);
|
||||
}
|
||||
|
||||
// copyBytesFrom: offset past end returns empty buffer
|
||||
{
|
||||
const u8 = new Uint8Array([1, 2, 3]);
|
||||
const b = Buffer.copyBytesFrom(u8, 10);
|
||||
assert.strictEqual(b.length, 0);
|
||||
}
|
||||
|
||||
// copyBytesFrom: Float64Array with offset and length
|
||||
{
|
||||
const f64 = new Float64Array([1.0, 2.0, 3.0, 4.0]);
|
||||
const b = Buffer.copyBytesFrom(f64, 1, 2);
|
||||
assert.strictEqual(b.length, 16);
|
||||
const view = new Float64Array(b.buffer, b.byteOffset, 2);
|
||||
assert.strictEqual(view[0], 2.0);
|
||||
assert.strictEqual(view[1], 3.0);
|
||||
}
|
||||
|
||||
// copyBytesFrom: empty typed array returns empty buffer
|
||||
{
|
||||
const empty = new Uint8Array(0);
|
||||
const b = Buffer.copyBytesFrom(empty);
|
||||
assert.strictEqual(b.length, 0);
|
||||
}
|
||||
|
||||
// Invalid encoding is allowed
|
||||
Buffer.from('asd', 1);
|
||||
@@ -0,0 +1,55 @@
|
||||
// Flags: --expose-internals --no-warnings --allow-natives-syntax
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
function testFastSwap16() {
|
||||
const buf = Buffer.from([0x01, 0x02, 0x03, 0x04]);
|
||||
const expected = Buffer.from([0x02, 0x01, 0x04, 0x03]);
|
||||
const padded = Buffer.alloc(256);
|
||||
buf.copy(padded);
|
||||
padded.swap16();
|
||||
assert.deepStrictEqual(padded.subarray(0, 4), expected);
|
||||
}
|
||||
|
||||
function testFastSwap32() {
|
||||
const buf = Buffer.from([0x01, 0x02, 0x03, 0x04]);
|
||||
const expected = Buffer.from([0x04, 0x03, 0x02, 0x01]);
|
||||
const padded = Buffer.alloc(256);
|
||||
buf.copy(padded);
|
||||
padded.swap32();
|
||||
assert.deepStrictEqual(padded.subarray(0, 4), expected);
|
||||
}
|
||||
|
||||
function testFastSwap64() {
|
||||
const buf = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
|
||||
const expected = Buffer.from([0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
|
||||
const padded = Buffer.alloc(256);
|
||||
buf.copy(padded);
|
||||
padded.swap64();
|
||||
assert.deepStrictEqual(padded.subarray(0, 8), expected);
|
||||
}
|
||||
|
||||
eval('%PrepareFunctionForOptimization(Buffer.prototype.swap16)');
|
||||
testFastSwap16();
|
||||
eval('%OptimizeFunctionOnNextCall(Buffer.prototype.swap16)');
|
||||
testFastSwap16();
|
||||
|
||||
eval('%PrepareFunctionForOptimization(Buffer.prototype.swap32)');
|
||||
testFastSwap32();
|
||||
eval('%OptimizeFunctionOnNextCall(Buffer.prototype.swap32)');
|
||||
testFastSwap32();
|
||||
|
||||
eval('%PrepareFunctionForOptimization(Buffer.prototype.swap64)');
|
||||
testFastSwap64();
|
||||
eval('%OptimizeFunctionOnNextCall(Buffer.prototype.swap64)');
|
||||
testFastSwap64();
|
||||
|
||||
if (common.isDebug) {
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const { getV8FastApiCallCount } = internalBinding('debug');
|
||||
assert.strictEqual(getV8FastApiCallCount('buffer.swap16'), 1);
|
||||
assert.strictEqual(getV8FastApiCallCount('buffer.swap32'), 1);
|
||||
assert.strictEqual(getV8FastApiCallCount('buffer.swap64'), 1);
|
||||
}
|
||||
@@ -78,6 +78,46 @@ assert.strictEqual(rangeBuffer.toString('ascii', 0, '-1.99'), '');
|
||||
assert.strictEqual(rangeBuffer.toString('ascii', 0, 1.99), 'a');
|
||||
assert.strictEqual(rangeBuffer.toString('ascii', 0, true), 'a');
|
||||
|
||||
// Test hex/base64/base64url partial range encoding (exercises V8 builtin path)
|
||||
{
|
||||
const buf = Buffer.from([0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe]);
|
||||
assert.strictEqual(buf.toString('hex'), 'deadbeefcafe');
|
||||
assert.strictEqual(buf.toString('hex', 0, 3), 'deadbe');
|
||||
assert.strictEqual(buf.toString('hex', 2, 5), 'beefca');
|
||||
assert.strictEqual(buf.toString('hex', 4), 'cafe');
|
||||
assert.strictEqual(buf.toString('hex', 6), '');
|
||||
assert.strictEqual(buf.toString('hex', 0, 0), '');
|
||||
}
|
||||
|
||||
{
|
||||
const buf = Buffer.from('Hello, World!');
|
||||
assert.strictEqual(buf.toString('base64'), 'SGVsbG8sIFdvcmxkIQ==');
|
||||
assert.strictEqual(buf.toString('base64', 0, 5), 'SGVsbG8=');
|
||||
assert.strictEqual(buf.toString('base64', 7), 'V29ybGQh');
|
||||
assert.strictEqual(buf.toString('base64', 0, 0), '');
|
||||
}
|
||||
|
||||
{
|
||||
const buf = Buffer.from('Hello, World!');
|
||||
assert.strictEqual(buf.toString('base64url'), 'SGVsbG8sIFdvcmxkIQ');
|
||||
assert.strictEqual(buf.toString('base64url', 0, 5), 'SGVsbG8');
|
||||
assert.strictEqual(buf.toString('base64url', 7), 'V29ybGQh');
|
||||
assert.strictEqual(buf.toString('base64url', 0, 0), '');
|
||||
}
|
||||
|
||||
// Test with pool-allocated buffer (has non-zero byteOffset)
|
||||
{
|
||||
const poolBuf = Buffer.from('test data for hex encoding');
|
||||
assert.strictEqual(
|
||||
poolBuf.toString('hex'),
|
||||
Buffer.from('test data for hex encoding').toString('hex')
|
||||
);
|
||||
assert.strictEqual(
|
||||
poolBuf.toString('hex', 5, 9),
|
||||
Buffer.from('data').toString('hex')
|
||||
);
|
||||
}
|
||||
|
||||
// Try toString() with an object as an encoding
|
||||
assert.strictEqual(rangeBuffer.toString({ toString: function() {
|
||||
return 'ascii';
|
||||
|
||||
Reference in new issue
Block a user