Browse Source

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>
v1.8.0-commit
Ben Noordhuis 10 years ago
parent
commit
bbf54a554a
  1. 202
      lib/buffer.js

202
lib/buffer.js

@ -24,82 +24,148 @@ function 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) {
if (!(this instanceof Buffer))
return new Buffer(subject, encoding);
return new Buffer(arg);
}
this.length = 0;
this.parent = undefined;
// Common case.
if (typeof(arg) === 'number') {
fromNumber(this, arg);
return;
}
if (typeof subject === 'number') {
this.length = +subject;
// Slightly less common case.
if (typeof(arg) === 'string') {
fromString(this, arg, arguments.length > 1 ? arguments[1] : 'utf8');
return;
}
// Unusual.
fromObject(this, arg);
}
} else if (typeof subject === 'string') {
if (typeof encoding !== 'string' || encoding.length === 0)
function fromNumber(that, length) {
allocate(that, length < 0 ? 0 : checked(length) | 0);
}
function fromString(that, string, encoding) {
if (typeof(encoding) !== 'string' || encoding === '')
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;
// Assumption: byteLength() return value is always < kMaxLength.
var length = byteLength(string, encoding) | 0;
allocate(that, length);
} else {
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);
}
if (this.length > kMaxLength) {
throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
'size: 0x' + kMaxLength.toString(16) + ' bytes');
function fromBuffer(that, buffer) {
var length = checked(buffer.length) | 0;
allocate(that, length);
buffer.copy(that, 0, 0, length);
}
if (this.length < 0)
this.length = 0;
else
this.length >>>= 0; // Coerce to uint32.
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;
}
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);
// 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;
}
if (typeof subject === 'number') {
return;
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;
}
if (typeof subject === 'string') {
// In the case of base64 it's possible that the size of the buffer
// allocated was slightly too large. In this case we need to rewrite
// the length to the actual length written.
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);
// 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);
} else if (subject instanceof Buffer) {
subject.copy(this, 0, 0, this.length);
for (var i = 0; i < length; i += 1)
that[i] = array[i] & 255;
}
} else if (typeof subject.length === 'number' || Array.isArray(subject)) {
// Really crappy way to handle Uint8Arrays, but V8 doesn't give a simple
// way to access the data from the C++ API.
for (var i = 0; i < this.length; i++)
this[i] = subject[i];
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) {
length = length >>> 0;
@ -197,30 +263,30 @@ Buffer.concat = function(list, length) {
};
Buffer.byteLength = function(str, enc) {
var ret;
str = str + '';
switch (enc) {
function byteLength(string, encoding) {
if (typeof(string) !== 'string')
string = String(string);
switch (encoding) {
case 'ascii':
case 'binary':
case 'raw':
ret = str.length;
break;
return string.length;
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
ret = str.length * 2;
break;
return string.length * 2;
case 'hex':
ret = str.length >>> 1;
break;
default:
ret = binding.byteLength(str, enc);
return string.length >>> 1;
}
return binding.byteLength(string, encoding);
}
return ret;
};
Buffer.byteLength = byteLength;
// toString(encoding, start=0, end=buffer.length)
Buffer.prototype.toString = function(encoding, start, end) {

Loading…
Cancel
Save