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

2 files changed

Lines changed: 58 additions & 57 deletions

File tree

‎lib/buffer.js‎

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22
'use strict';
33

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

8+
classFastBufferextendsUint8Array{}
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
constflags=bindingObj.flags;
6369
constkNoZeroFill=0;
6470

65-
functioncreateBuffer(size,noZeroFill){
66-
flags[kNoZeroFill]=noZeroFill ? 1 : 0;
71+
functioncreateUnsafeBuffer(size){
72+
flags[kNoZeroFill]=1;
6773
try{
68-
constui8=newUint8Array(size);
69-
Object.setPrototypeOf(ui8,Buffer.prototype);
70-
returnui8;
74+
returnnewFastBuffer(size);
7175
}finally{
7276
flags[kNoZeroFill]=0;
7377
}
7478
}
7579

7680
functioncreatePool(){
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
returnfromObject(value);
134138
};
135139

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

139142
functionassertSize(size){
@@ -153,18 +156,16 @@ function assertSize(size) {
153156
**/
154157
Buffer.alloc=function(size,fill,encoding){
155158
assertSize(size);
156-
if(size<=0)
157-
returncreateBuffer(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-
returntypeofencoding==='string' ?
164-
createBuffer(size,true).fill(fill,encoding) :
165-
createBuffer(size,true).fill(fill);
164+
if(typeofencoding!=='string')
165+
encoding=undefined;
166+
returncreateUnsafeBuffer(size).fill(fill,encoding);
166167
}
167-
returncreateBuffer(size);
168+
returnnewFastBuffer(size);
168169
};
169170

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

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

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

201202
functionallocate(size){
202203
if(size<=0){
203-
returncreateBuffer(0);
204+
returnnewFastBuffer();
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-
returncreateBuffer(size,true);
217+
returncreateUnsafeBuffer(size);
217218
}
218219
}
219220

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

228229
if(string.length===0)
229-
returnBuffer.alloc(0);
230+
returnnewFastBuffer();
230231

231232
varlength=byteLength(string,encoding);
232233

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

253254
functionfromArrayBuffer(obj,byteOffset,length){
255+
if(!isArrayBuffer(obj))
256+
thrownewTypeError('argument is not an ArrayBuffer');
257+
254258
byteOffset>>>=0;
255259

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

259-
length>>>=0;
260-
returnbinding.createFromArrayBuffer(obj,byteOffset,length);
273+
returnnewFastBuffer(obj,byteOffset,length);
261274
}
262275

263276
functionfromObject(obj){
@@ -274,7 +287,7 @@ function fromObject(obj) {
274287
if(obj){
275288
if(obj.bufferinstanceofArrayBuffer||'length'inobj){
276289
if(typeofobj.length!=='number'||obj.length!==obj.length){
277-
returnallocate(0);
290+
returnnewFastBuffer();
278291
}
279292
returnfromArrayLike(obj);
280293
}
@@ -341,7 +354,7 @@ Buffer.concat = function(list, length) {
341354
thrownewTypeError('"list" argument must be an Array of Buffers');
342355

343356
if(list.length===0)
344-
returnBuffer.alloc(0);
357+
returnnewFastBuffer();
345358

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

820833

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

827856

‎src/node_buffer.cc‎

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

429429

430-
voidCreateFromArrayBuffer(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
voidStringSlice(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)