From 456942a920fe313ebe0b0da366d26ef400ec177e Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Wed, 17 Apr 2013 16:29:14 -0700 Subject: [PATCH] buffer: reimplement Buffer pools While the new Buffer implementation is much faster we still have the necessity of using Buffer pools. This is undesirable because it may still lead to unwanted memory retention, but for the time being this is the best solution. Because of this re-introduction, and since there is no more SlowBuffer type, the SlowBuffer method has been re-purposed to return a non-pooled Buffer instance. This will be helpful for developers to store data for indeterminate lengths of time without introducing a memory leak. Another change to Buffer pools was that they are only allocated if the requested chunk is < poolSize / 2. This was done because allocations are much quicker now, and it's a better use of the pool. --- doc/api/buffer.markdown | 28 ++++++++++++++++++++++++---- lib/buffer.js | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/doc/api/buffer.markdown b/doc/api/buffer.markdown index 335ac4dcc31..d880911f0d9 100644 --- a/doc/api/buffer.markdown +++ b/doc/api/buffer.markdown @@ -694,8 +694,28 @@ Note that this is a property on the buffer module returned by ## Class: SlowBuffer -Deprecated. SlowBuffer now returns an instance of Buffer. +Returns an un-pooled `Buffer`. -In order to avoid the overhead of allocating many C++ Buffer objects for -small blocks of memory in the lifetime of a server, Node allocates memory -in 8Kb (8192 byte) chunks. This is now handled by Smalloc. +In order to avoid the garbage collection overhead of creating many individually +allocated Buffers, by default allocations under 4KB are sliced from a single +larger allocated object. This approach improves both performance and memory +usage since v8 does not need to track and cleanup as many `Persistent` objects. + +In the case where a developer may need to retain a small chunk of memory from a +pool for an indeterminate amount of time it may be appropriate to create an +un-pooled Buffer instance using SlowBuffer and copy out the relevant bits. + + // need to keep around a few small chunks of memory + var store = []; + + socket.on('readable', function() { + var data = socket.read(); + // allocate for retained data + var sb = new SlowBuffer(10); + // copy the data into the new allocation + data.copy(sb, 0, 0, 10); + store.push(sb); + }); + +Though this should used sparingly and only be a last resort *after* a developer +has actively observed undue memory retention in their applications. diff --git a/lib/buffer.js b/lib/buffer.js index a5abf368af6..e0ce4c545ee 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -27,13 +27,25 @@ var sliceOnto = smalloc.sliceOnto; var kMaxLength = smalloc.kMaxLength; exports.Buffer = Buffer; -// backwards compatibility (DEPRECATE) -exports.SlowBuffer = Buffer; +exports.SlowBuffer = SlowBuffer; exports.INSPECT_MAX_BYTES = 50; // add methods to Buffer prototype buffer.setupBufferJS(Buffer); +Buffer.poolSize = 8 * 1024; +var poolSize = Buffer.poolSize; +var poolOffset = 0; +var allocPool = alloc({}, poolSize); + + +function createPool() { + poolSize = Buffer.poolSize; + allocPool = alloc({}, poolSize); + poolOffset = 0; +} + + function Buffer(subject, encoding) { if (!(this instanceof Buffer)) return new Buffer(subject, encoding); @@ -67,7 +79,17 @@ function Buffer(subject, encoding) { if (this.length > kMaxLength) throw new RangeError('length > kMaxLength'); - alloc(this, this.length); + if (this.length < Buffer.poolSize / 2 && 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); + } if (type !== 'number') { if (type === 'string') { @@ -84,6 +106,14 @@ function Buffer(subject, encoding) { } +function SlowBuffer(length) { + length = ~~length; + var b = new Buffer(undefined, length); + alloc(b, length); + return b; +} + + // Static methods Buffer.isBuffer = function isBuffer(b) {