Skip to content

Commit ac8e1bf

Browse files
committed
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: #7349 Ref: #6893 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com> Reviewed-By: Trevor Norris <trev.norris@gmail.com>
1 parent c544213 commit ac8e1bf

File tree

2 files changed

+58
-57
lines changed

2 files changed

+58
-57
lines changed

lib/buffer.js

+58-29
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22
'use strict';
33

44
const binding = process.binding('buffer');
5+
const { isArrayBuffer } = process.binding('util');
56
const bindingObj = {};
67

8+
class FastBuffer extends Uint8Array {}
9+
10+
FastBuffer.prototype.constructor = Buffer;
11+
Buffer.prototype = FastBuffer.prototype;
12+
713
exports.Buffer = Buffer;
814
exports.SlowBuffer = SlowBuffer;
915
exports.INSPECT_MAX_BYTES = 50;
@@ -62,20 +68,18 @@ Buffer.prototype.swap32 = function swap32() {
6268
const flags = bindingObj.flags;
6369
const kNoZeroFill = 0;
6470

65-
function createBuffer(size, noZeroFill) {
66-
flags[kNoZeroFill] = noZeroFill ? 1 : 0;
71+
function createUnsafeBuffer(size) {
72+
flags[kNoZeroFill] = 1;
6773
try {
68-
const ui8 = new Uint8Array(size);
69-
Object.setPrototypeOf(ui8, Buffer.prototype);
70-
return ui8;
74+
return new FastBuffer(size);
7175
} finally {
7276
flags[kNoZeroFill] = 0;
7377
}
7478
}
7579

7680
function createPool() {
7781
poolSize = Buffer.poolSize;
78-
allocPool = createBuffer(poolSize, true);
82+
allocPool = createUnsafeBuffer(poolSize);
7983
poolOffset = 0;
8084
}
8185
createPool();
@@ -133,7 +137,6 @@ Buffer.from = function(value, encodingOrOffset, length) {
133137
return fromObject(value);
134138
};
135139

136-
Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype);
137140
Object.setPrototypeOf(Buffer, Uint8Array);
138141

139142
function assertSize(size) {
@@ -153,18 +156,16 @@ function assertSize(size) {
153156
**/
154157
Buffer.alloc = function(size, fill, encoding) {
155158
assertSize(size);
156-
if (size <= 0)
157-
return createBuffer(size);
158-
if (fill !== undefined) {
159+
if (size > 0 && fill !== undefined) {
159160
// Since we are filling anyway, don't zero fill initially.
160161
// Only pay attention to encoding if it's a string. This
161162
// prevents accidentally sending in a number that would
162163
// be interpretted as a start offset.
163-
return typeof encoding === 'string' ?
164-
createBuffer(size, true).fill(fill, encoding) :
165-
createBuffer(size, true).fill(fill);
164+
if (typeof encoding !== 'string')
165+
encoding = undefined;
166+
return createUnsafeBuffer(size).fill(fill, encoding);
166167
}
167-
return createBuffer(size);
168+
return new FastBuffer(size);
168169
};
169170

170171
/**
@@ -183,15 +184,15 @@ Buffer.allocUnsafe = function(size) {
183184
**/
184185
Buffer.allocUnsafeSlow = function(size) {
185186
assertSize(size);
186-
return createBuffer(size, true);
187+
return createUnsafeBuffer(size);
187188
};
188189

189190
// If --zero-fill-buffers command line argument is set, a zero-filled
190191
// buffer is returned.
191192
function SlowBuffer(length) {
192193
if (+length != length)
193194
length = 0;
194-
return createBuffer(+length, true);
195+
return createUnsafeBuffer(+length);
195196
}
196197

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

201202
function allocate(size) {
202203
if (size <= 0) {
203-
return createBuffer(0);
204+
return new FastBuffer();
204205
}
205206
if (size < (Buffer.poolSize >>> 1)) {
206207
if (size > (poolSize - poolOffset))
@@ -213,7 +214,7 @@ function allocate(size) {
213214
// Even though this is checked above, the conditional is a safety net and
214215
// sanity check to prevent any subsequent typed array allocation from not
215216
// being zero filled.
216-
return createBuffer(size, true);
217+
return createUnsafeBuffer(size);
217218
}
218219
}
219220

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

228229
if (string.length === 0)
229-
return Buffer.alloc(0);
230+
return new FastBuffer();
230231

231232
var length = byteLength(string, encoding);
232233

@@ -246,18 +247,30 @@ function fromArrayLike(obj) {
246247
const length = obj.length;
247248
const b = allocate(length);
248249
for (var i = 0; i < length; i++)
249-
b[i] = obj[i] & 255;
250+
b[i] = obj[i];
250251
return b;
251252
}
252253

253254
function fromArrayBuffer(obj, byteOffset, length) {
255+
if (!isArrayBuffer(obj))
256+
throw new TypeError('argument is not an ArrayBuffer');
257+
254258
byteOffset >>>= 0;
255259

256-
if (typeof length === 'undefined')
257-
return binding.createFromArrayBuffer(obj, byteOffset);
260+
const maxLength = obj.byteLength - byteOffset;
261+
262+
if (maxLength <= 0)
263+
throw new RangeError("'offset' is out of bounds");
264+
265+
if (length === undefined) {
266+
length = maxLength;
267+
} else {
268+
length >>>= 0;
269+
if (length > maxLength)
270+
throw new RangeError("'length' is out of bounds");
271+
}
258272

259-
length >>>= 0;
260-
return binding.createFromArrayBuffer(obj, byteOffset, length);
273+
return new FastBuffer(obj, byteOffset, length);
261274
}
262275

263276
function fromObject(obj) {
@@ -274,7 +287,7 @@ function fromObject(obj) {
274287
if (obj) {
275288
if (obj.buffer instanceof ArrayBuffer || 'length' in obj) {
276289
if (typeof obj.length !== 'number' || obj.length !== obj.length) {
277-
return allocate(0);
290+
return new FastBuffer();
278291
}
279292
return fromArrayLike(obj);
280293
}
@@ -341,7 +354,7 @@ Buffer.concat = function(list, length) {
341354
throw new TypeError('"list" argument must be an Array of Buffers');
342355

343356
if (list.length === 0)
344-
return Buffer.alloc(0);
357+
return new FastBuffer();
345358

346359
if (length === undefined) {
347360
length = 0;
@@ -818,10 +831,26 @@ Buffer.prototype.toJSON = function() {
818831
};
819832

820833

834+
function adjustOffset(offset, length) {
835+
offset = +offset;
836+
if (offset === 0 || Number.isNaN(offset)) {
837+
return 0;
838+
}
839+
if (offset < 0) {
840+
offset += length;
841+
return offset > 0 ? offset : 0;
842+
} else {
843+
return offset < length ? offset : length;
844+
}
845+
}
846+
847+
821848
Buffer.prototype.slice = function slice(start, end) {
822-
const buffer = this.subarray(start, end);
823-
Object.setPrototypeOf(buffer, Buffer.prototype);
824-
return buffer;
849+
const srcLength = this.length;
850+
start = adjustOffset(start, srcLength);
851+
end = end !== undefined ? adjustOffset(end, srcLength) : srcLength;
852+
const newLength = end > start ? end - start : 0;
853+
return new FastBuffer(this.buffer, this.byteOffset + start, newLength);
825854
};
826855

827856

src/node_buffer.cc

-28
Original file line numberDiff line numberDiff line change
@@ -427,33 +427,6 @@ void CreateFromString(const FunctionCallbackInfo<Value>& args) {
427427
}
428428

429429

430-
void CreateFromArrayBuffer(const FunctionCallbackInfo<Value>& args) {
431-
Environment* env = Environment::GetCurrent(args);
432-
if (!args[0]->IsArrayBuffer())
433-
return env->ThrowTypeError("argument is not an ArrayBuffer");
434-
Local<ArrayBuffer> ab = args[0].As<ArrayBuffer>();
435-
436-
size_t ab_length = ab->ByteLength();
437-
size_t offset;
438-
size_t max_length;
439-
440-
CHECK_NOT_OOB(ParseArrayIndex(args[1], 0, &offset));
441-
CHECK_NOT_OOB(ParseArrayIndex(args[2], ab_length - offset, &max_length));
442-
443-
if (offset >= ab_length)
444-
return env->ThrowRangeError("'offset' is out of bounds");
445-
if (max_length > ab_length - offset)
446-
return env->ThrowRangeError("'length' is out of bounds");
447-
448-
Local<Uint8Array> ui = Uint8Array::New(ab, offset, max_length);
449-
Maybe<bool> mb =
450-
ui->SetPrototype(env->context(), env->buffer_prototype_object());
451-
if (!mb.FromMaybe(false))
452-
return env->ThrowError("Unable to set Object prototype");
453-
args.GetReturnValue().Set(ui);
454-
}
455-
456-
457430
template <encoding encoding>
458431
void StringSlice(const FunctionCallbackInfo<Value>& args) {
459432
Environment* env = Environment::GetCurrent(args);
@@ -1246,7 +1219,6 @@ void Initialize(Local<Object> target,
12461219

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

12511223
env->SetMethod(target, "byteLengthUtf8", ByteLengthUtf8);
12521224
env->SetMethod(target, "compare", Compare);

0 commit comments

Comments
 (0)