From 47a5310ccc737366868cb84ddd499f018610e570 Mon Sep 17 00:00:00 2001 From: Vladimir Kurchatkin Date: Tue, 23 Dec 2014 21:03:54 +0300 Subject: [PATCH 1/3] buffer: implement `iterable` interface This makes possible to use `for..of` loop with buffers. Also related `keys`, `values` and `entries` methods are added for feature parity with `Uint8Array`. --- doc/api/buffer.markdown | 29 ++++++++++ lib/buffer.js | 54 ++++++++++++++++++ test/parallel/test-buffer-iterator.js | 82 +++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 test/parallel/test-buffer-iterator.js diff --git a/doc/api/buffer.markdown b/doc/api/buffer.markdown index 2269345adf97b8..f68361868405da 100644 --- a/doc/api/buffer.markdown +++ b/doc/api/buffer.markdown @@ -797,6 +797,19 @@ buffer. var b = new Buffer(50); b.fill("h"); +### buffer.values() + +Creates iterator for buffer values (bytes). This function is called automatically +when `buffer` is used in a `for..of` statement. + +### buffer.keys() + +Creates iterator for buffer keys (indices). + +### buffer.entries() + +Creates iterator for `[index, byte]` arrays. + ## buffer.INSPECT_MAX_BYTES * Number, Default: 50 @@ -807,6 +820,22 @@ be overridden by user modules. Note that this is a property on the buffer module returned by `require('buffer')`, not on the Buffer global, or a buffer instance. +## ES6 iteration + +Buffers can be iterated over using `for..of` syntax: + + var buf = new Buffer([1, 2, 3]); + + for (var b of buf) + console.log(b) + + // 1 + // 2 + // 3 + +Additionally, `buffer.values()`, `buffer.keys()` and `buffer.entries()` +methods can be used to create iterators. + ## Class: SlowBuffer Returns an un-pooled `Buffer`. diff --git a/lib/buffer.js b/lib/buffer.js index 461a39eca6d345..17961f4480929b 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -930,3 +930,57 @@ Buffer.prototype.writeDoubleBE = function writeDoubleBE(val, offset, noAssert) { internal.writeDoubleBE(this, val, offset); return offset + 8; }; + +// ES6 iterator + +var ITERATOR_KIND_KEYS = 1; +var ITERATOR_KIND_VALUES = 2; +var ITERATOR_KIND_ENTRIES = 3; + +function BufferIterator(buffer, kind) { + this._buffer = buffer; + this._kind = kind; + this._index = 0; +} + +function BufferIteratorResult(value, done) { + this.value = value; + this.done = done; +} + +BufferIterator.prototype.next = function() { + var buffer = this._buffer; + var kind = this._kind; + var index = this._index; + + if (index >= buffer.length) + return new BufferIteratorResult(undefined, true); + + this._index++; + + if (kind === ITERATOR_KIND_VALUES) + return new BufferIteratorResult(buffer[index], false); + + if (kind === ITERATOR_KIND_ENTRIES) + return new BufferIteratorResult([index, buffer[index]], false); + + return new BufferIteratorResult(index, false); +}; + +BufferIterator.prototype[Symbol.iterator] = function() { + return this; +}; + +Buffer.prototype.keys = function() { + return new BufferIterator(this, ITERATOR_KIND_KEYS); +}; + +Buffer.prototype.entries = function() { + return new BufferIterator(this, ITERATOR_KIND_ENTRIES); +}; + +Buffer.prototype.values = function() { + return new BufferIterator(this, ITERATOR_KIND_VALUES); +}; + +Buffer.prototype[Symbol.iterator] = Buffer.prototype.values; diff --git a/test/parallel/test-buffer-iterator.js b/test/parallel/test-buffer-iterator.js new file mode 100644 index 00000000000000..b3dd3bf27aabf4 --- /dev/null +++ b/test/parallel/test-buffer-iterator.js @@ -0,0 +1,82 @@ +// Copyright io.js contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); + +var buffer = new Buffer([1, 2, 3, 4, 5]); +var arr; +var b; + +// buffers should be iterable + +arr = []; + +for (b of buffer) + arr.push(b); + +assert.deepEqual(arr, [1, 2, 3, 4, 5]); + + +// buffer iterators should be iterable + +arr = []; + +for (b of buffer[Symbol.iterator]()) + arr.push(b); + +assert.deepEqual(arr, [1, 2, 3, 4, 5]); + + +// buffer#values() should return iterator for values + +arr = []; + +for (b of buffer.values()) + arr.push(b); + +assert.deepEqual(arr, [1, 2, 3, 4, 5]); + + +// buffer#keys() should return iterator for keys + +arr = []; + +for (b of buffer.keys()) + arr.push(b); + +assert.deepEqual(arr, [0, 1, 2, 3, 4]); + + +// buffer#entries() should return iterator for entries + +arr = []; + +for (var b of buffer.entries()) + arr.push(b); + +assert.deepEqual(arr, [ + [0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5] +]); From d67e2f77a763f5948eb059e847505e09b66ac1be Mon Sep 17 00:00:00 2001 From: Vladimir Kurchatkin Date: Mon, 29 Dec 2014 11:10:50 +0300 Subject: [PATCH 2/3] buffer: iterator optimization --- benchmark/buffers/buffer-iterate.js | 43 +++++++++++++++++++++++++ lib/buffer.js | 45 ++++++++++++++++++++------- test/parallel/test-buffer-iterator.js | 23 +------------- 3 files changed, 78 insertions(+), 33 deletions(-) create mode 100644 benchmark/buffers/buffer-iterate.js diff --git a/benchmark/buffers/buffer-iterate.js b/benchmark/buffers/buffer-iterate.js new file mode 100644 index 00000000000000..103fbe10e94a85 --- /dev/null +++ b/benchmark/buffers/buffer-iterate.js @@ -0,0 +1,43 @@ +var SlowBuffer = require('buffer').SlowBuffer; +var common = require('../common.js'); +var assert = require('assert'); + +var bench = common.createBenchmark(main, { + size: [16, 512, 1024, 4096, 16386], + type: ['fast', 'slow'], + method: ['for', 'forOf'], + n: [1e3] +}); + +function main(conf) { + var len = +conf.size; + var clazz = conf.type === 'fast' ? Buffer : SlowBuffer; + var buffer = new clazz(len); + buffer.fill(0); + + if (conf.method === 'for') + benchFor(buffer, conf.n); + else + benchForOf(buffer, conf.n); +} + + +function benchFor(buffer, n) { + bench.start(); + + for (var k = 0; k < n; k++) + for (var i = 0; i < buffer.length; i++) + assert(buffer[i] === 0); + + bench.end(n); +} + +function benchForOf(buffer, n) { + bench.start(); + + for (var k = 0; k < n; k++) + for (var b of buffer) + assert(b === 0); + + bench.end(n); +} diff --git a/lib/buffer.js b/lib/buffer.js index 17961f4480929b..3130817178779f 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -934,43 +934,66 @@ Buffer.prototype.writeDoubleBE = function writeDoubleBE(val, offset, noAssert) { // ES6 iterator var ITERATOR_KIND_KEYS = 1; -var ITERATOR_KIND_VALUES = 2; var ITERATOR_KIND_ENTRIES = 3; +function BufferIteratorResult(value, done) { + this.value = value; + this.done = done; +} + +var resultCache = new Array(256); + +for (var i = 0; i < 256; i++) + resultCache[i] = Object.freeze(new BufferIteratorResult(i, false)); + +var finalResult = Object.freeze(new BufferIteratorResult(undefined, true)); + function BufferIterator(buffer, kind) { this._buffer = buffer; this._kind = kind; this._index = 0; } -function BufferIteratorResult(value, done) { - this.value = value; - this.done = done; -} - BufferIterator.prototype.next = function() { var buffer = this._buffer; var kind = this._kind; var index = this._index; if (index >= buffer.length) - return new BufferIteratorResult(undefined, true); + return finalResult; this._index++; - if (kind === ITERATOR_KIND_VALUES) - return new BufferIteratorResult(buffer[index], false); - if (kind === ITERATOR_KIND_ENTRIES) return new BufferIteratorResult([index, buffer[index]], false); return new BufferIteratorResult(index, false); }; +function BufferValueIterator(buffer) { + BufferIterator.call(this, buffer, null); +} + +BufferValueIterator.prototype.next = function() { + var buffer = this._buffer; + var index = this._index; + + if (index >= buffer.length) + return finalResult; + + this._index++; + + return resultCache[buffer[index]]; +}; + + BufferIterator.prototype[Symbol.iterator] = function() { return this; }; +BufferValueIterator.prototype[Symbol.iterator] = + BufferIterator.prototype[Symbol.iterator]; + Buffer.prototype.keys = function() { return new BufferIterator(this, ITERATOR_KIND_KEYS); }; @@ -980,7 +1003,7 @@ Buffer.prototype.entries = function() { }; Buffer.prototype.values = function() { - return new BufferIterator(this, ITERATOR_KIND_VALUES); + return new BufferValueIterator(this); }; Buffer.prototype[Symbol.iterator] = Buffer.prototype.values; diff --git a/test/parallel/test-buffer-iterator.js b/test/parallel/test-buffer-iterator.js index b3dd3bf27aabf4..ab02934bf9e794 100644 --- a/test/parallel/test-buffer-iterator.js +++ b/test/parallel/test-buffer-iterator.js @@ -1,24 +1,3 @@ -// Copyright io.js contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - var common = require('../common'); var assert = require('assert'); @@ -31,7 +10,7 @@ var b; arr = []; for (b of buffer) - arr.push(b); + arr.push(b); assert.deepEqual(arr, [1, 2, 3, 4, 5]); From fb70ba270ce526b08a2ae7ba6e7d140f3929aa0e Mon Sep 17 00:00:00 2001 From: Vladimir Kurchatkin Date: Wed, 21 Jan 2015 20:06:10 +0300 Subject: [PATCH 3/3] benchmark: add manual iteration case --- benchmark/buffers/buffer-iterate.js | 30 ++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/benchmark/buffers/buffer-iterate.js b/benchmark/buffers/buffer-iterate.js index 103fbe10e94a85..c4fc4e7a4ac8fc 100644 --- a/benchmark/buffers/buffer-iterate.js +++ b/benchmark/buffers/buffer-iterate.js @@ -5,20 +5,23 @@ var assert = require('assert'); var bench = common.createBenchmark(main, { size: [16, 512, 1024, 4096, 16386], type: ['fast', 'slow'], - method: ['for', 'forOf'], + method: ['for', 'forOf', 'iterator'], n: [1e3] }); +var methods = { + 'for': benchFor, + 'forOf': benchForOf, + 'iterator': benchIterator +}; + function main(conf) { var len = +conf.size; var clazz = conf.type === 'fast' ? Buffer : SlowBuffer; var buffer = new clazz(len); buffer.fill(0); - if (conf.method === 'for') - benchFor(buffer, conf.n); - else - benchForOf(buffer, conf.n); + methods[conf.method](buffer, conf.n); } @@ -41,3 +44,20 @@ function benchForOf(buffer, n) { bench.end(n); } + +function benchIterator(buffer, n) { + bench.start(); + + for (var k = 0; k < n; k++) { + var iter = buffer[Symbol.iterator](); + var cur = iter.next(); + + while (!cur.done) { + assert(cur.value === 0); + cur = iter.next(); + } + + } + + bench.end(n); +}