buffer: improve creation performance.
Improves performance of allocating unsafe buffers, creating buffers from an existing ArrayBuffer and creating .slice(...) from existing Buffer by avoiding deoptimizing change of prototype after Uint8Array allocation in favor of ES6 native subclassing. This is done through an internal ES6 class that extends Uint8Array and is used for allocations, but the regular Buffer function is exposed, so calling Buffer(...) with or without `new` continues to work as usual and prototype chains are also preserved. Performance wins for .slice are +120% (2.2x), and, consequently, for unsafe allocations up to +95% (1.9x) for small buffers, and for safe allocations (zero-filled) up to +30% (1.3x). PR-URL: https://github.com/nodejs/node/pull/6893 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
This commit is contained in:
parent
de4161d367
commit
5292a1358f
@ -2,8 +2,14 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const binding = process.binding('buffer');
|
const binding = process.binding('buffer');
|
||||||
|
const { isArrayBuffer } = process.binding('util');
|
||||||
const bindingObj = {};
|
const bindingObj = {};
|
||||||
|
|
||||||
|
class FastBuffer extends Uint8Array {}
|
||||||
|
|
||||||
|
FastBuffer.prototype.constructor = Buffer;
|
||||||
|
Buffer.prototype = FastBuffer.prototype;
|
||||||
|
|
||||||
exports.Buffer = Buffer;
|
exports.Buffer = Buffer;
|
||||||
exports.SlowBuffer = SlowBuffer;
|
exports.SlowBuffer = SlowBuffer;
|
||||||
exports.INSPECT_MAX_BYTES = 50;
|
exports.INSPECT_MAX_BYTES = 50;
|
||||||
@ -63,24 +69,18 @@ Buffer.prototype.swap32 = function swap32() {
|
|||||||
// do not own the ArrayBuffer allocator. Zero fill is always on in that case.
|
// do not own the ArrayBuffer allocator. Zero fill is always on in that case.
|
||||||
const zeroFill = bindingObj.zeroFill || [0];
|
const zeroFill = bindingObj.zeroFill || [0];
|
||||||
|
|
||||||
function createBuffer(size, noZeroFill) {
|
function createUnsafeBuffer(size) {
|
||||||
if (noZeroFill)
|
|
||||||
zeroFill[0] = 0;
|
zeroFill[0] = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var ui8 = new Uint8Array(size);
|
return new FastBuffer(size);
|
||||||
} finally {
|
} finally {
|
||||||
if (noZeroFill)
|
|
||||||
zeroFill[0] = 1;
|
zeroFill[0] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.setPrototypeOf(ui8, Buffer.prototype);
|
|
||||||
return ui8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPool() {
|
function createPool() {
|
||||||
poolSize = Buffer.poolSize;
|
poolSize = Buffer.poolSize;
|
||||||
allocPool = createBuffer(poolSize, true);
|
allocPool = createUnsafeBuffer(poolSize);
|
||||||
poolOffset = 0;
|
poolOffset = 0;
|
||||||
}
|
}
|
||||||
createPool();
|
createPool();
|
||||||
@ -138,7 +138,6 @@ Buffer.from = function(value, encodingOrOffset, length) {
|
|||||||
return fromObject(value);
|
return fromObject(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype);
|
|
||||||
Object.setPrototypeOf(Buffer, Uint8Array);
|
Object.setPrototypeOf(Buffer, Uint8Array);
|
||||||
|
|
||||||
function assertSize(size) {
|
function assertSize(size) {
|
||||||
@ -158,18 +157,16 @@ function assertSize(size) {
|
|||||||
**/
|
**/
|
||||||
Buffer.alloc = function(size, fill, encoding) {
|
Buffer.alloc = function(size, fill, encoding) {
|
||||||
assertSize(size);
|
assertSize(size);
|
||||||
if (size <= 0)
|
if (size > 0 && fill !== undefined) {
|
||||||
return createBuffer(size);
|
|
||||||
if (fill !== undefined) {
|
|
||||||
// Since we are filling anyway, don't zero fill initially.
|
// Since we are filling anyway, don't zero fill initially.
|
||||||
// Only pay attention to encoding if it's a string. This
|
// Only pay attention to encoding if it's a string. This
|
||||||
// prevents accidentally sending in a number that would
|
// prevents accidentally sending in a number that would
|
||||||
// be interpretted as a start offset.
|
// be interpretted as a start offset.
|
||||||
return typeof encoding === 'string' ?
|
if (typeof encoding !== 'string')
|
||||||
createBuffer(size, true).fill(fill, encoding) :
|
encoding = undefined;
|
||||||
createBuffer(size, true).fill(fill);
|
return createUnsafeBuffer(size).fill(fill, encoding);
|
||||||
}
|
}
|
||||||
return createBuffer(size);
|
return new FastBuffer(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,7 +185,7 @@ Buffer.allocUnsafe = function(size) {
|
|||||||
**/
|
**/
|
||||||
Buffer.allocUnsafeSlow = function(size) {
|
Buffer.allocUnsafeSlow = function(size) {
|
||||||
assertSize(size);
|
assertSize(size);
|
||||||
return createBuffer(size, true);
|
return createUnsafeBuffer(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
// If --zero-fill-buffers command line argument is set, a zero-filled
|
// If --zero-fill-buffers command line argument is set, a zero-filled
|
||||||
@ -196,7 +193,7 @@ Buffer.allocUnsafeSlow = function(size) {
|
|||||||
function SlowBuffer(length) {
|
function SlowBuffer(length) {
|
||||||
if (+length != length)
|
if (+length != length)
|
||||||
length = 0;
|
length = 0;
|
||||||
return createBuffer(+length, true);
|
return createUnsafeBuffer(+length);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.setPrototypeOf(SlowBuffer.prototype, Uint8Array.prototype);
|
Object.setPrototypeOf(SlowBuffer.prototype, Uint8Array.prototype);
|
||||||
@ -205,7 +202,7 @@ Object.setPrototypeOf(SlowBuffer, Uint8Array);
|
|||||||
|
|
||||||
function allocate(size) {
|
function allocate(size) {
|
||||||
if (size <= 0) {
|
if (size <= 0) {
|
||||||
return createBuffer(0);
|
return new FastBuffer();
|
||||||
}
|
}
|
||||||
if (size < (Buffer.poolSize >>> 1)) {
|
if (size < (Buffer.poolSize >>> 1)) {
|
||||||
if (size > (poolSize - poolOffset))
|
if (size > (poolSize - poolOffset))
|
||||||
@ -218,7 +215,7 @@ function allocate(size) {
|
|||||||
// Even though this is checked above, the conditional is a safety net and
|
// Even though this is checked above, the conditional is a safety net and
|
||||||
// sanity check to prevent any subsequent typed array allocation from not
|
// sanity check to prevent any subsequent typed array allocation from not
|
||||||
// being zero filled.
|
// being zero filled.
|
||||||
return createBuffer(size, true);
|
return createUnsafeBuffer(size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +228,7 @@ function fromString(string, encoding) {
|
|||||||
throw new TypeError('"encoding" must be a valid string encoding');
|
throw new TypeError('"encoding" must be a valid string encoding');
|
||||||
|
|
||||||
if (string.length === 0)
|
if (string.length === 0)
|
||||||
return Buffer.alloc(0);
|
return new FastBuffer();
|
||||||
|
|
||||||
var length = byteLength(string, encoding);
|
var length = byteLength(string, encoding);
|
||||||
|
|
||||||
@ -251,18 +248,30 @@ function fromArrayLike(obj) {
|
|||||||
const length = obj.length;
|
const length = obj.length;
|
||||||
const b = allocate(length);
|
const b = allocate(length);
|
||||||
for (var i = 0; i < length; i++)
|
for (var i = 0; i < length; i++)
|
||||||
b[i] = obj[i] & 255;
|
b[i] = obj[i];
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromArrayBuffer(obj, byteOffset, length) {
|
function fromArrayBuffer(obj, byteOffset, length) {
|
||||||
|
if (!isArrayBuffer(obj))
|
||||||
|
throw new TypeError('argument is not an ArrayBuffer');
|
||||||
|
|
||||||
byteOffset >>>= 0;
|
byteOffset >>>= 0;
|
||||||
|
|
||||||
if (typeof length === 'undefined')
|
const maxLength = obj.byteLength - byteOffset;
|
||||||
return binding.createFromArrayBuffer(obj, byteOffset);
|
|
||||||
|
|
||||||
|
if (maxLength <= 0)
|
||||||
|
throw new RangeError("'offset' is out of bounds");
|
||||||
|
|
||||||
|
if (length === undefined) {
|
||||||
|
length = maxLength;
|
||||||
|
} else {
|
||||||
length >>>= 0;
|
length >>>= 0;
|
||||||
return binding.createFromArrayBuffer(obj, byteOffset, length);
|
if (length > maxLength)
|
||||||
|
throw new RangeError("'length' is out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FastBuffer(obj, byteOffset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromObject(obj) {
|
function fromObject(obj) {
|
||||||
@ -279,7 +288,7 @@ function fromObject(obj) {
|
|||||||
if (obj) {
|
if (obj) {
|
||||||
if (obj.buffer instanceof ArrayBuffer || 'length' in obj) {
|
if (obj.buffer instanceof ArrayBuffer || 'length' in obj) {
|
||||||
if (typeof obj.length !== 'number' || obj.length !== obj.length) {
|
if (typeof obj.length !== 'number' || obj.length !== obj.length) {
|
||||||
return allocate(0);
|
return new FastBuffer();
|
||||||
}
|
}
|
||||||
return fromArrayLike(obj);
|
return fromArrayLike(obj);
|
||||||
}
|
}
|
||||||
@ -346,7 +355,7 @@ Buffer.concat = function(list, length) {
|
|||||||
throw new TypeError('"list" argument must be an Array of Buffers');
|
throw new TypeError('"list" argument must be an Array of Buffers');
|
||||||
|
|
||||||
if (list.length === 0)
|
if (list.length === 0)
|
||||||
return Buffer.alloc(0);
|
return new FastBuffer();
|
||||||
|
|
||||||
if (length === undefined) {
|
if (length === undefined) {
|
||||||
length = 0;
|
length = 0;
|
||||||
@ -823,10 +832,26 @@ Buffer.prototype.toJSON = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function adjustOffset(offset, length) {
|
||||||
|
offset = +offset;
|
||||||
|
if (offset === 0 || Number.isNaN(offset)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (offset < 0) {
|
||||||
|
offset += length;
|
||||||
|
return offset > 0 ? offset : 0;
|
||||||
|
} else {
|
||||||
|
return offset < length ? offset : length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Buffer.prototype.slice = function slice(start, end) {
|
Buffer.prototype.slice = function slice(start, end) {
|
||||||
const buffer = this.subarray(start, end);
|
const srcLength = this.length;
|
||||||
Object.setPrototypeOf(buffer, Buffer.prototype);
|
start = adjustOffset(start, srcLength);
|
||||||
return buffer;
|
end = end !== undefined ? adjustOffset(end, srcLength) : srcLength;
|
||||||
|
const newLength = end > start ? end - start : 0;
|
||||||
|
return new FastBuffer(this.buffer, this.byteOffset + start, newLength);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -427,33 +427,6 @@ void CreateFromString(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CreateFromArrayBuffer(const FunctionCallbackInfo<Value>& args) {
|
|
||||||
Environment* env = Environment::GetCurrent(args);
|
|
||||||
if (!args[0]->IsArrayBuffer())
|
|
||||||
return env->ThrowTypeError("argument is not an ArrayBuffer");
|
|
||||||
Local<ArrayBuffer> ab = args[0].As<ArrayBuffer>();
|
|
||||||
|
|
||||||
size_t ab_length = ab->ByteLength();
|
|
||||||
size_t offset;
|
|
||||||
size_t max_length;
|
|
||||||
|
|
||||||
CHECK_NOT_OOB(ParseArrayIndex(args[1], 0, &offset));
|
|
||||||
CHECK_NOT_OOB(ParseArrayIndex(args[2], ab_length - offset, &max_length));
|
|
||||||
|
|
||||||
if (offset >= ab_length)
|
|
||||||
return env->ThrowRangeError("'offset' is out of bounds");
|
|
||||||
if (max_length > ab_length - offset)
|
|
||||||
return env->ThrowRangeError("'length' is out of bounds");
|
|
||||||
|
|
||||||
Local<Uint8Array> ui = Uint8Array::New(ab, offset, max_length);
|
|
||||||
Maybe<bool> mb =
|
|
||||||
ui->SetPrototype(env->context(), env->buffer_prototype_object());
|
|
||||||
if (!mb.FromMaybe(false))
|
|
||||||
return env->ThrowError("Unable to set Object prototype");
|
|
||||||
args.GetReturnValue().Set(ui);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template <encoding encoding>
|
template <encoding encoding>
|
||||||
void StringSlice(const FunctionCallbackInfo<Value>& args) {
|
void StringSlice(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
@ -1244,7 +1217,6 @@ void Initialize(Local<Object> target,
|
|||||||
|
|
||||||
env->SetMethod(target, "setupBufferJS", SetupBufferJS);
|
env->SetMethod(target, "setupBufferJS", SetupBufferJS);
|
||||||
env->SetMethod(target, "createFromString", CreateFromString);
|
env->SetMethod(target, "createFromString", CreateFromString);
|
||||||
env->SetMethod(target, "createFromArrayBuffer", CreateFromArrayBuffer);
|
|
||||||
|
|
||||||
env->SetMethod(target, "byteLengthUtf8", ByteLengthUtf8);
|
env->SetMethod(target, "byteLengthUtf8", ByteLengthUtf8);
|
||||||
env->SetMethod(target, "compare", Compare);
|
env->SetMethod(target, "compare", Compare);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user