lib: hand-optimize Buffer constructor
The Buffer constructor is used pervasively throughout io.js, yet it was one of the most unwieldy functions in core. This commit breaks up the constructor into several small functions in a way that makes V8 happy. About 8-10% CPU time was attributed to the constructor function before in buffer-heavy benchmarks. That pretty much drops to zero now because V8 can now easily inline it at the call site. It shortens the running time of the following simple benchmark by about 15%: for (var i = 0; i < 25e6; ++i) new Buffer(1); And about 8% from this benchmark: for (var i = 0; i < 1e7; ++i) new Buffer('x', 'ucs2'); PR-URL: https://github.com/iojs/io.js/pull/1048 Reviewed-By: Trevor Norris <trev.norris@gmail.com>
This commit is contained in:
parent
3446ff417b
commit
bbf54a554a
224
lib/buffer.js
224
lib/buffer.js
@ -24,82 +24,148 @@ function createPool() {
|
|||||||
}
|
}
|
||||||
createPool();
|
createPool();
|
||||||
|
|
||||||
|
function Buffer(arg) {
|
||||||
|
if (!(this instanceof Buffer)) {
|
||||||
|
// Avoid going through an ArgumentsAdaptorTrampoline in the common case.
|
||||||
|
if (arguments.length > 1)
|
||||||
|
return new Buffer(arg, arguments[1]);
|
||||||
|
|
||||||
function Buffer(subject, encoding) {
|
return new Buffer(arg);
|
||||||
if (!(this instanceof Buffer))
|
|
||||||
return new Buffer(subject, encoding);
|
|
||||||
|
|
||||||
if (typeof subject === 'number') {
|
|
||||||
this.length = +subject;
|
|
||||||
|
|
||||||
} else if (typeof subject === 'string') {
|
|
||||||
if (typeof encoding !== 'string' || encoding.length === 0)
|
|
||||||
encoding = 'utf8';
|
|
||||||
this.length = Buffer.byteLength(subject, encoding);
|
|
||||||
|
|
||||||
// Handle Arrays, Buffers, Uint8Arrays or JSON.
|
|
||||||
} else if (subject !== null && typeof subject === 'object') {
|
|
||||||
if (subject.type === 'Buffer' && Array.isArray(subject.data))
|
|
||||||
subject = subject.data;
|
|
||||||
this.length = +subject.length;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new TypeError('must start with number, buffer, array or string');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.length > kMaxLength) {
|
this.length = 0;
|
||||||
throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
|
|
||||||
'size: 0x' + kMaxLength.toString(16) + ' bytes');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.length < 0)
|
|
||||||
this.length = 0;
|
|
||||||
else
|
|
||||||
this.length >>>= 0; // Coerce to uint32.
|
|
||||||
|
|
||||||
this.parent = undefined;
|
this.parent = undefined;
|
||||||
if (this.length <= (Buffer.poolSize >>> 1) && this.length > 0) {
|
|
||||||
if (this.length > poolSize - poolOffset)
|
|
||||||
createPool();
|
|
||||||
this.parent = sliceOnto(allocPool,
|
|
||||||
this,
|
|
||||||
poolOffset,
|
|
||||||
poolOffset + this.length);
|
|
||||||
poolOffset += this.length;
|
|
||||||
} else {
|
|
||||||
alloc(this, this.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof subject === 'number') {
|
// Common case.
|
||||||
|
if (typeof(arg) === 'number') {
|
||||||
|
fromNumber(this, arg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof subject === 'string') {
|
// Slightly less common case.
|
||||||
// In the case of base64 it's possible that the size of the buffer
|
if (typeof(arg) === 'string') {
|
||||||
// allocated was slightly too large. In this case we need to rewrite
|
fromString(this, arg, arguments.length > 1 ? arguments[1] : 'utf8');
|
||||||
// the length to the actual length written.
|
return;
|
||||||
var len = this.write(subject, encoding);
|
}
|
||||||
// Buffer was truncated after decode, realloc internal ExternalArray
|
|
||||||
if (len !== this.length) {
|
|
||||||
var prevLen = this.length;
|
|
||||||
this.length = len;
|
|
||||||
truncate(this, this.length);
|
|
||||||
// Only need to readjust the poolOffset if the allocation is a slice.
|
|
||||||
if (this.parent != undefined)
|
|
||||||
poolOffset -= (prevLen - len);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (subject instanceof Buffer) {
|
// Unusual.
|
||||||
subject.copy(this, 0, 0, this.length);
|
fromObject(this, arg);
|
||||||
|
}
|
||||||
|
|
||||||
} else if (typeof subject.length === 'number' || Array.isArray(subject)) {
|
function fromNumber(that, length) {
|
||||||
// Really crappy way to handle Uint8Arrays, but V8 doesn't give a simple
|
allocate(that, length < 0 ? 0 : checked(length) | 0);
|
||||||
// way to access the data from the C++ API.
|
}
|
||||||
for (var i = 0; i < this.length; i++)
|
|
||||||
this[i] = subject[i];
|
function fromString(that, string, encoding) {
|
||||||
|
if (typeof(encoding) !== 'string' || encoding === '')
|
||||||
|
encoding = 'utf8';
|
||||||
|
|
||||||
|
// Assumption: byteLength() return value is always < kMaxLength.
|
||||||
|
var length = byteLength(string, encoding) | 0;
|
||||||
|
allocate(that, length);
|
||||||
|
|
||||||
|
var actual = that.write(string, encoding) | 0;
|
||||||
|
if (actual !== length) {
|
||||||
|
// Fix up for truncated base64 input. Don't bother returning
|
||||||
|
// the unused two or three bytes to the pool.
|
||||||
|
that.length = actual;
|
||||||
|
truncate(that, actual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fromObject(that, object) {
|
||||||
|
if (object instanceof Buffer)
|
||||||
|
return fromBuffer(that, object);
|
||||||
|
|
||||||
|
if (Array.isArray(object))
|
||||||
|
return fromArray(that, object);
|
||||||
|
|
||||||
|
if (object == null)
|
||||||
|
throw new TypeError('must start with number, buffer, array or string');
|
||||||
|
|
||||||
|
if (object.buffer instanceof ArrayBuffer)
|
||||||
|
return fromTypedArray(that, object);
|
||||||
|
|
||||||
|
if (object.length)
|
||||||
|
return fromArrayLike(that, object);
|
||||||
|
|
||||||
|
return fromJsonObject(that, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromBuffer(that, buffer) {
|
||||||
|
var length = checked(buffer.length) | 0;
|
||||||
|
allocate(that, length);
|
||||||
|
buffer.copy(that, 0, 0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromArray(that, array) {
|
||||||
|
var length = checked(array.length) | 0;
|
||||||
|
allocate(that, length);
|
||||||
|
for (var i = 0; i < length; i += 1)
|
||||||
|
that[i] = array[i] & 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplicate of fromArray() to keep fromArray() monomorphic.
|
||||||
|
function fromTypedArray(that, array) {
|
||||||
|
var length = checked(array.length) | 0;
|
||||||
|
allocate(that, length);
|
||||||
|
// Truncating the elements is probably not what people expect from typed
|
||||||
|
// arrays with BYTES_PER_ELEMENT > 1 but it's compatible with the behavior
|
||||||
|
// of the old Buffer constructor.
|
||||||
|
for (var i = 0; i < length; i += 1)
|
||||||
|
that[i] = array[i] & 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromArrayLike(that, array) {
|
||||||
|
var length = checked(array.length) | 0;
|
||||||
|
allocate(that, length);
|
||||||
|
for (var i = 0; i < length; i += 1)
|
||||||
|
that[i] = array[i] & 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize { type: 'Buffer', data: [1,2,3,...] } into a Buffer object.
|
||||||
|
// Returns a zero-length buffer for inputs that don't conform to the spec.
|
||||||
|
function fromJsonObject(that, object) {
|
||||||
|
var array;
|
||||||
|
var length = 0;
|
||||||
|
|
||||||
|
if (object.type === 'Buffer' && Array.isArray(object.data)) {
|
||||||
|
array = object.data;
|
||||||
|
length = checked(array.length) | 0;
|
||||||
|
}
|
||||||
|
allocate(that, length);
|
||||||
|
|
||||||
|
for (var i = 0; i < length; i += 1)
|
||||||
|
that[i] = array[i] & 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
function allocate(that, length) {
|
||||||
|
var fromPool = length !== 0 && length <= Buffer.poolSize >>> 1;
|
||||||
|
that.parent = fromPool ? palloc(that, length) : alloc(that, length);
|
||||||
|
that.length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function palloc(that, length) {
|
||||||
|
if (length > poolSize - poolOffset)
|
||||||
|
createPool();
|
||||||
|
|
||||||
|
var start = poolOffset;
|
||||||
|
var end = start + length;
|
||||||
|
var buf = sliceOnto(allocPool, that, start, end);
|
||||||
|
poolOffset = end;
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checked(length) {
|
||||||
|
// Note: cannot use `length < kMaxLength` here because that fails when
|
||||||
|
// length is NaN (which is otherwise coerced to zero.)
|
||||||
|
if (length >= kMaxLength) {
|
||||||
|
throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
|
||||||
|
'size: 0x' + kMaxLength.toString(16) + ' bytes');
|
||||||
|
}
|
||||||
|
return length >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
function SlowBuffer(length) {
|
function SlowBuffer(length) {
|
||||||
length = length >>> 0;
|
length = length >>> 0;
|
||||||
@ -197,30 +263,30 @@ Buffer.concat = function(list, length) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Buffer.byteLength = function(str, enc) {
|
function byteLength(string, encoding) {
|
||||||
var ret;
|
if (typeof(string) !== 'string')
|
||||||
str = str + '';
|
string = String(string);
|
||||||
switch (enc) {
|
|
||||||
|
switch (encoding) {
|
||||||
case 'ascii':
|
case 'ascii':
|
||||||
case 'binary':
|
case 'binary':
|
||||||
case 'raw':
|
case 'raw':
|
||||||
ret = str.length;
|
return string.length;
|
||||||
break;
|
|
||||||
case 'ucs2':
|
case 'ucs2':
|
||||||
case 'ucs-2':
|
case 'ucs-2':
|
||||||
case 'utf16le':
|
case 'utf16le':
|
||||||
case 'utf-16le':
|
case 'utf-16le':
|
||||||
ret = str.length * 2;
|
return string.length * 2;
|
||||||
break;
|
|
||||||
case 'hex':
|
|
||||||
ret = str.length >>> 1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ret = binding.byteLength(str, enc);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
case 'hex':
|
||||||
|
return string.length >>> 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding.byteLength(string, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer.byteLength = byteLength;
|
||||||
|
|
||||||
// toString(encoding, start=0, end=buffer.length)
|
// toString(encoding, start=0, end=buffer.length)
|
||||||
Buffer.prototype.toString = function(encoding, start, end) {
|
Buffer.prototype.toString = function(encoding, start, end) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user