Skip to content

Commit 5292a13

Browse files
RReverseraddaleax
authored andcommitted
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: #6893 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Сковорода Никита Андреевич <[email protected]>
1 parent de4161d commit 5292a13

File tree

2 files changed

+59
-62
lines changed

2 files changed

+59
-62
lines changed

lib/buffer.js

+59-34
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;
@@ -63,24 +69,18 @@ Buffer.prototype.swap32 = function swap32() {
6369
// do not own the ArrayBuffer allocator. Zero fill is always on in that case.
6470
const zeroFill = bindingObj.zeroFill || [0];
6571

66-
function createBuffer(size, noZeroFill) {
67-
if (noZeroFill)
68-
zeroFill[0] = 0;
69-
72+
function createUnsafeBuffer(size) {
73+
zeroFill[0] = 0;
7074
try {
71-
var ui8 = new Uint8Array(size);
75+
return new FastBuffer(size);
7276
} finally {
73-
if (noZeroFill)
74-
zeroFill[0] = 1;
77+
zeroFill[0] = 1;
7578
}
76-
77-
Object.setPrototypeOf(ui8, Buffer.prototype);
78-
return ui8;
7979
}
8080

8181
function createPool() {
8282
poolSize = Buffer.poolSize;
83-
allocPool = createBuffer(poolSize, true);
83+
allocPool = createUnsafeBuffer(poolSize);
8484
poolOffset = 0;
8585
}
8686
createPool();
@@ -138,7 +138,6 @@ Buffer.from = function(value, encodingOrOffset, length) {
138138
return fromObject(value);
139139
};
140140

141-
Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype);
142141
Object.setPrototypeOf(Buffer, Uint8Array);
143142

144143
function assertSize(size) {
@@ -158,18 +157,16 @@ function assertSize(size) {
158157
**/
159158
Buffer.alloc = function(size, fill, encoding) {
160159
assertSize(size);
161-
if (size <= 0)
162-
return createBuffer(size);
163-
if (fill !== undefined) {
160+
if (size > 0 && fill !== undefined) {
164161
// Since we are filling anyway, don't zero fill initially.
165162
// Only pay attention to encoding if it's a string. This
166163
// prevents accidentally sending in a number that would
167164
// be interpretted as a start offset.
168-
return typeof encoding === 'string' ?
169-
createBuffer(size, true).fill(fill, encoding) :
170-
createBuffer(size, true).fill(fill);
165+
if (typeof encoding !== 'string')
166+
encoding = undefined;
167+
return createUnsafeBuffer(size).fill(fill, encoding);
171168
}
172-
return createBuffer(size);
169+
return new FastBuffer(size);
173170
};
174171

175172
/**
@@ -188,15 +185,15 @@ Buffer.allocUnsafe = function(size) {
188185
**/
189186
Buffer.allocUnsafeSlow = function(size) {
190187
assertSize(size);
191-
return createBuffer(size, true);
188+
return createUnsafeBuffer(size);
192189
};
193190

194191
// If --zero-fill-buffers command line argument is set, a zero-filled
195192
// buffer is returned.
196193
function SlowBuffer(length) {
197194
if (+length != length)
198195
length = 0;
199-
return createBuffer(+length, true);
196+
return createUnsafeBuffer(+length);
200197
}
201198

202199
Object.setPrototypeOf(SlowBuffer.prototype, Uint8Array.prototype);
@@ -205,7 +202,7 @@ Object.setPrototypeOf(SlowBuffer, Uint8Array);
205202

206203
function allocate(size) {
207204
if (size <= 0) {
208-
return createBuffer(0);
205+
return new FastBuffer();
209206
}
210207
if (size < (Buffer.poolSize >>> 1)) {
211208
if (size > (poolSize - poolOffset))
@@ -218,7 +215,7 @@ function allocate(size) {
218215
// Even though this is checked above, the conditional is a safety net and
219216
// sanity check to prevent any subsequent typed array allocation from not
220217
// being zero filled.
221-
return createBuffer(size, true);
218+
return createUnsafeBuffer(size);
222219
}
223220
}
224221

@@ -231,7 +228,7 @@ function fromString(string, encoding) {
231228
throw new TypeError('"encoding" must be a valid string encoding');
232229

233230
if (string.length === 0)
234-
return Buffer.alloc(0);
231+
return new FastBuffer();
235232

236233
var length = byteLength(string, encoding);
237234

@@ -251,18 +248,30 @@ function fromArrayLike(obj) {
251248
const length = obj.length;
252249
const b = allocate(length);
253250
for (var i = 0; i < length; i++)
254-
b[i] = obj[i] & 255;
251+
b[i] = obj[i];
255252
return b;
256253
}
257254

258255
function fromArrayBuffer(obj, byteOffset, length) {
256+
if (!isArrayBuffer(obj))
257+
throw new TypeError('argument is not an ArrayBuffer');
258+
259259
byteOffset >>>= 0;
260260

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

264-
length >>>= 0;
265-
return binding.createFromArrayBuffer(obj, byteOffset, length);
274+
return new FastBuffer(obj, byteOffset, length);
266275
}
267276

268277
function fromObject(obj) {
@@ -279,7 +288,7 @@ function fromObject(obj) {
279288
if (obj) {
280289
if (obj.buffer instanceof ArrayBuffer || 'length' in obj) {
281290
if (typeof obj.length !== 'number' || obj.length !== obj.length) {
282-
return allocate(0);
291+
return new FastBuffer();
283292
}
284293
return fromArrayLike(obj);
285294
}
@@ -346,7 +355,7 @@ Buffer.concat = function(list, length) {
346355
throw new TypeError('"list" argument must be an Array of Buffers');
347356

348357
if (list.length === 0)
349-
return Buffer.alloc(0);
358+
return new FastBuffer();
350359

351360
if (length === undefined) {
352361
length = 0;
@@ -823,10 +832,26 @@ Buffer.prototype.toJSON = function() {
823832
};
824833

825834

835+
function adjustOffset(offset, length) {
836+
offset = +offset;
837+
if (offset === 0 || Number.isNaN(offset)) {
838+
return 0;
839+
}
840+
if (offset < 0) {
841+
offset += length;
842+
return offset > 0 ? offset : 0;
843+
} else {
844+
return offset < length ? offset : length;
845+
}
846+
}
847+
848+
826849
Buffer.prototype.slice = function slice(start, end) {
827-
const buffer = this.subarray(start, end);
828-
Object.setPrototypeOf(buffer, Buffer.prototype);
829-
return buffer;
850+
const srcLength = this.length;
851+
start = adjustOffset(start, srcLength);
852+
end = end !== undefined ? adjustOffset(end, srcLength) : srcLength;
853+
const newLength = end > start ? end - start : 0;
854+
return new FastBuffer(this.buffer, this.byteOffset + start, newLength);
830855
};
831856

832857

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);
@@ -1244,7 +1217,6 @@ void Initialize(Local<Object> target,
12441217

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

12491221
env->SetMethod(target, "byteLengthUtf8", ByteLengthUtf8);
12501222
env->SetMethod(target, "compare", Compare);

0 commit comments

Comments
 (0)