Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport 6893 for v6.x (buffer: improve creation performance) #7349

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 58 additions & 29 deletions lib/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@
'use strict';

const binding = process.binding('buffer');
const { isArrayBuffer } = process.binding('util');
const bindingObj = {};

class FastBuffer extends Uint8Array {}

FastBuffer.prototype.constructor = Buffer;
Buffer.prototype = FastBuffer.prototype;

exports.Buffer = Buffer;
exports.SlowBuffer = SlowBuffer;
exports.INSPECT_MAX_BYTES = 50;
Expand Down Expand Up @@ -62,20 +68,18 @@ Buffer.prototype.swap32 = function swap32() {
const flags = bindingObj.flags;
const kNoZeroFill = 0;

function createBuffer(size, noZeroFill) {
flags[kNoZeroFill] = noZeroFill ? 1 : 0;
function createUnsafeBuffer(size) {
flags[kNoZeroFill] = 1;
try {
const ui8 = new Uint8Array(size);
Object.setPrototypeOf(ui8, Buffer.prototype);
return ui8;
return new FastBuffer(size);
} finally {
flags[kNoZeroFill] = 0;
}
}

function createPool() {
poolSize = Buffer.poolSize;
allocPool = createBuffer(poolSize, true);
allocPool = createUnsafeBuffer(poolSize);
poolOffset = 0;
}
createPool();
Expand Down Expand Up @@ -133,7 +137,6 @@ Buffer.from = function(value, encodingOrOffset, length) {
return fromObject(value);
};

Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype);
Object.setPrototypeOf(Buffer, Uint8Array);

function assertSize(size) {
Expand All @@ -153,18 +156,16 @@ function assertSize(size) {
**/
Buffer.alloc = function(size, fill, encoding) {
assertSize(size);
if (size <= 0)
return createBuffer(size);
if (fill !== undefined) {
if (size > 0 && fill !== undefined) {
// Since we are filling anyway, don't zero fill initially.
// Only pay attention to encoding if it's a string. This
// prevents accidentally sending in a number that would
// be interpretted as a start offset.
return typeof encoding === 'string' ?
createBuffer(size, true).fill(fill, encoding) :
createBuffer(size, true).fill(fill);
if (typeof encoding !== 'string')
encoding = undefined;
return createUnsafeBuffer(size).fill(fill, encoding);
}
return createBuffer(size);
return new FastBuffer(size);
};

/**
Expand All @@ -183,15 +184,15 @@ Buffer.allocUnsafe = function(size) {
**/
Buffer.allocUnsafeSlow = function(size) {
assertSize(size);
return createBuffer(size, true);
return createUnsafeBuffer(size);
};

// If --zero-fill-buffers command line argument is set, a zero-filled
// buffer is returned.
function SlowBuffer(length) {
if (+length != length)
length = 0;
return createBuffer(+length, true);
return createUnsafeBuffer(+length);
}

Object.setPrototypeOf(SlowBuffer.prototype, Uint8Array.prototype);
Expand All @@ -200,7 +201,7 @@ Object.setPrototypeOf(SlowBuffer, Uint8Array);

function allocate(size) {
if (size <= 0) {
return createBuffer(0);
return new FastBuffer();
}
if (size < (Buffer.poolSize >>> 1)) {
if (size > (poolSize - poolOffset))
Expand All @@ -213,7 +214,7 @@ function allocate(size) {
// Even though this is checked above, the conditional is a safety net and
// sanity check to prevent any subsequent typed array allocation from not
// being zero filled.
return createBuffer(size, true);
return createUnsafeBuffer(size);
}
}

Expand All @@ -226,7 +227,7 @@ function fromString(string, encoding) {
throw new TypeError('"encoding" must be a valid string encoding');

if (string.length === 0)
return Buffer.alloc(0);
return new FastBuffer();

var length = byteLength(string, encoding);

Expand All @@ -246,18 +247,30 @@ function fromArrayLike(obj) {
const length = obj.length;
const b = allocate(length);
for (var i = 0; i < length; i++)
b[i] = obj[i] & 255;
b[i] = obj[i];
return b;
}

function fromArrayBuffer(obj, byteOffset, length) {
if (!isArrayBuffer(obj))
throw new TypeError('argument is not an ArrayBuffer');

byteOffset >>>= 0;

if (typeof length === 'undefined')
return binding.createFromArrayBuffer(obj, byteOffset);
const maxLength = obj.byteLength - byteOffset;

if (maxLength <= 0)
throw new RangeError("'offset' is out of bounds");

if (length === undefined) {
length = maxLength;
} else {
length >>>= 0;
if (length > maxLength)
throw new RangeError("'length' is out of bounds");
}

length >>>= 0;
return binding.createFromArrayBuffer(obj, byteOffset, length);
return new FastBuffer(obj, byteOffset, length);
}

function fromObject(obj) {
Expand All @@ -274,7 +287,7 @@ function fromObject(obj) {
if (obj) {
if (obj.buffer instanceof ArrayBuffer || 'length' in obj) {
if (typeof obj.length !== 'number' || obj.length !== obj.length) {
return allocate(0);
return new FastBuffer();
}
return fromArrayLike(obj);
}
Expand Down Expand Up @@ -341,7 +354,7 @@ Buffer.concat = function(list, length) {
throw new TypeError('"list" argument must be an Array of Buffers');

if (list.length === 0)
return Buffer.alloc(0);
return new FastBuffer();

if (length === undefined) {
length = 0;
Expand Down Expand Up @@ -818,10 +831,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) {
const buffer = this.subarray(start, end);
Object.setPrototypeOf(buffer, Buffer.prototype);
return buffer;
const srcLength = this.length;
start = adjustOffset(start, srcLength);
end = end !== undefined ? adjustOffset(end, srcLength) : srcLength;
const newLength = end > start ? end - start : 0;
return new FastBuffer(this.buffer, this.byteOffset + start, newLength);
};


Expand Down
28 changes: 0 additions & 28 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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>
void StringSlice(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Expand Down Expand Up @@ -1246,7 +1219,6 @@ void Initialize(Local<Object> target,

env->SetMethod(target, "setupBufferJS", SetupBufferJS);
env->SetMethod(target, "createFromString", CreateFromString);
env->SetMethod(target, "createFromArrayBuffer", CreateFromArrayBuffer);

env->SetMethod(target, "byteLengthUtf8", ByteLengthUtf8);
env->SetMethod(target, "compare", Compare);
Expand Down