Skip to content

Commit bbf54a5

Browse files
committed
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: #1048 Reviewed-By: Trevor Norris <[email protected]>
1 parent 3446ff4 commit bbf54a5

File tree

1 file changed

+140
-74
lines changed

1 file changed

+140
-74
lines changed

lib/buffer.js

+140-74
Original file line numberDiff line numberDiff line change
@@ -24,82 +24,148 @@ function createPool() {
2424
}
2525
createPool();
2626

27+
function Buffer(arg) {
28+
if (!(this instanceof Buffer)) {
29+
// Avoid going through an ArgumentsAdaptorTrampoline in the common case.
30+
if (arguments.length > 1)
31+
return new Buffer(arg, arguments[1]);
2732

28-
function Buffer(subject, encoding) {
29-
if (!(this instanceof Buffer))
30-
return new Buffer(subject, encoding);
31-
32-
if (typeof subject === 'number') {
33-
this.length = +subject;
34-
35-
} else if (typeof subject === 'string') {
36-
if (typeof encoding !== 'string' || encoding.length === 0)
37-
encoding = 'utf8';
38-
this.length = Buffer.byteLength(subject, encoding);
33+
return new Buffer(arg);
34+
}
3935

40-
// Handle Arrays, Buffers, Uint8Arrays or JSON.
41-
} else if (subject !== null && typeof subject === 'object') {
42-
if (subject.type === 'Buffer' && Array.isArray(subject.data))
43-
subject = subject.data;
44-
this.length = +subject.length;
36+
this.length = 0;
37+
this.parent = undefined;
4538

46-
} else {
47-
throw new TypeError('must start with number, buffer, array or string');
39+
// Common case.
40+
if (typeof(arg) === 'number') {
41+
fromNumber(this, arg);
42+
return;
4843
}
4944

50-
if (this.length > kMaxLength) {
51-
throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
52-
'size: 0x' + kMaxLength.toString(16) + ' bytes');
45+
// Slightly less common case.
46+
if (typeof(arg) === 'string') {
47+
fromString(this, arg, arguments.length > 1 ? arguments[1] : 'utf8');
48+
return;
5349
}
5450

55-
if (this.length < 0)
56-
this.length = 0;
57-
else
58-
this.length >>>= 0; // Coerce to uint32.
51+
// Unusual.
52+
fromObject(this, arg);
53+
}
5954

60-
this.parent = undefined;
61-
if (this.length <= (Buffer.poolSize >>> 1) && this.length > 0) {
62-
if (this.length > poolSize - poolOffset)
63-
createPool();
64-
this.parent = sliceOnto(allocPool,
65-
this,
66-
poolOffset,
67-
poolOffset + this.length);
68-
poolOffset += this.length;
69-
} else {
70-
alloc(this, this.length);
71-
}
55+
function fromNumber(that, length) {
56+
allocate(that, length < 0 ? 0 : checked(length) | 0);
57+
}
7258

73-
if (typeof subject === 'number') {
74-
return;
59+
function fromString(that, string, encoding) {
60+
if (typeof(encoding) !== 'string' || encoding === '')
61+
encoding = 'utf8';
62+
63+
// Assumption: byteLength() return value is always < kMaxLength.
64+
var length = byteLength(string, encoding) | 0;
65+
allocate(that, length);
66+
67+
var actual = that.write(string, encoding) | 0;
68+
if (actual !== length) {
69+
// Fix up for truncated base64 input. Don't bother returning
70+
// the unused two or three bytes to the pool.
71+
that.length = actual;
72+
truncate(that, actual);
7573
}
74+
}
7675

77-
if (typeof subject === 'string') {
78-
// In the case of base64 it's possible that the size of the buffer
79-
// allocated was slightly too large. In this case we need to rewrite
80-
// the length to the actual length written.
81-
var len = this.write(subject, encoding);
82-
// Buffer was truncated after decode, realloc internal ExternalArray
83-
if (len !== this.length) {
84-
var prevLen = this.length;
85-
this.length = len;
86-
truncate(this, this.length);
87-
// Only need to readjust the poolOffset if the allocation is a slice.
88-
if (this.parent != undefined)
89-
poolOffset -= (prevLen - len);
90-
}
76+
function fromObject(that, object) {
77+
if (object instanceof Buffer)
78+
return fromBuffer(that, object);
79+
80+
if (Array.isArray(object))
81+
return fromArray(that, object);
82+
83+
if (object == null)
84+
throw new TypeError('must start with number, buffer, array or string');
9185

92-
} else if (subject instanceof Buffer) {
93-
subject.copy(this, 0, 0, this.length);
86+
if (object.buffer instanceof ArrayBuffer)
87+
return fromTypedArray(that, object);
88+
89+
if (object.length)
90+
return fromArrayLike(that, object);
91+
92+
return fromJsonObject(that, object);
93+
}
94+
95+
function fromBuffer(that, buffer) {
96+
var length = checked(buffer.length) | 0;
97+
allocate(that, length);
98+
buffer.copy(that, 0, 0, length);
99+
}
100+
101+
function fromArray(that, array) {
102+
var length = checked(array.length) | 0;
103+
allocate(that, length);
104+
for (var i = 0; i < length; i += 1)
105+
that[i] = array[i] & 255;
106+
}
94107

95-
} else if (typeof subject.length === 'number' || Array.isArray(subject)) {
96-
// Really crappy way to handle Uint8Arrays, but V8 doesn't give a simple
97-
// way to access the data from the C++ API.
98-
for (var i = 0; i < this.length; i++)
99-
this[i] = subject[i];
108+
// Duplicate of fromArray() to keep fromArray() monomorphic.
109+
function fromTypedArray(that, array) {
110+
var length = checked(array.length) | 0;
111+
allocate(that, length);
112+
// Truncating the elements is probably not what people expect from typed
113+
// arrays with BYTES_PER_ELEMENT > 1 but it's compatible with the behavior
114+
// of the old Buffer constructor.
115+
for (var i = 0; i < length; i += 1)
116+
that[i] = array[i] & 255;
117+
}
118+
119+
function fromArrayLike(that, array) {
120+
var length = checked(array.length) | 0;
121+
allocate(that, length);
122+
for (var i = 0; i < length; i += 1)
123+
that[i] = array[i] & 255;
124+
}
125+
126+
// Deserialize { type: 'Buffer', data: [1,2,3,...] } into a Buffer object.
127+
// Returns a zero-length buffer for inputs that don't conform to the spec.
128+
function fromJsonObject(that, object) {
129+
var array;
130+
var length = 0;
131+
132+
if (object.type === 'Buffer' && Array.isArray(object.data)) {
133+
array = object.data;
134+
length = checked(array.length) | 0;
100135
}
136+
allocate(that, length);
137+
138+
for (var i = 0; i < length; i += 1)
139+
that[i] = array[i] & 255;
140+
}
141+
142+
function allocate(that, length) {
143+
var fromPool = length !== 0 && length <= Buffer.poolSize >>> 1;
144+
that.parent = fromPool ? palloc(that, length) : alloc(that, length);
145+
that.length = length;
101146
}
102147

148+
function palloc(that, length) {
149+
if (length > poolSize - poolOffset)
150+
createPool();
151+
152+
var start = poolOffset;
153+
var end = start + length;
154+
var buf = sliceOnto(allocPool, that, start, end);
155+
poolOffset = end;
156+
157+
return buf;
158+
}
159+
160+
function checked(length) {
161+
// Note: cannot use `length < kMaxLength` here because that fails when
162+
// length is NaN (which is otherwise coerced to zero.)
163+
if (length >= kMaxLength) {
164+
throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
165+
'size: 0x' + kMaxLength.toString(16) + ' bytes');
166+
}
167+
return length >>> 0;
168+
}
103169

104170
function SlowBuffer(length) {
105171
length = length >>> 0;
@@ -197,30 +263,30 @@ Buffer.concat = function(list, length) {
197263
};
198264

199265

200-
Buffer.byteLength = function(str, enc) {
201-
var ret;
202-
str = str + '';
203-
switch (enc) {
266+
function byteLength(string, encoding) {
267+
if (typeof(string) !== 'string')
268+
string = String(string);
269+
270+
switch (encoding) {
204271
case 'ascii':
205272
case 'binary':
206273
case 'raw':
207-
ret = str.length;
208-
break;
274+
return string.length;
275+
209276
case 'ucs2':
210277
case 'ucs-2':
211278
case 'utf16le':
212279
case 'utf-16le':
213-
ret = str.length * 2;
214-
break;
280+
return string.length * 2;
281+
215282
case 'hex':
216-
ret = str.length >>> 1;
217-
break;
218-
default:
219-
ret = binding.byteLength(str, enc);
283+
return string.length >>> 1;
220284
}
221-
return ret;
222-
};
223285

286+
return binding.byteLength(string, encoding);
287+
}
288+
289+
Buffer.byteLength = byteLength;
224290

225291
// toString(encoding, start=0, end=buffer.length)
226292
Buffer.prototype.toString = function(encoding, start, end) {

0 commit comments

Comments
 (0)