From b28644b776310de51bb74628af6baa53d71007f6 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Sat, 10 Aug 2019 00:22:06 +0300 Subject: [PATCH] Improve performance of toArrayLike (#222) * add toArray to benchmark * optimize BN#toArrayLike * split BN#toArrayLike * fix bounds in toArrayLike* * add more tests for toArrayLike * save 1 op in toArrayLike* --- benchmarks/index.js | 6 +++ lib/bn.js | 96 +++++++++++++++++++++++++++++++++------------ test/utils-test.js | 10 ++++- 3 files changed, 86 insertions(+), 26 deletions(-) diff --git a/benchmarks/index.js b/benchmarks/index.js index f099256..dcdf6fe 100644 --- a/benchmarks/index.js +++ b/benchmarks/index.js @@ -452,4 +452,10 @@ add('bitLength', { } }); +add('toArray', { + 'bn.js': function () { + fixture.a1j.toArray(); + } +}); + start(); diff --git a/lib/bn.js b/lib/bn.js index d658233..2be2c25 100644 --- a/lib/bn.js +++ b/lib/bn.js @@ -540,51 +540,97 @@ return this.toArrayLike(Array, endian, length); }; + var allocate = function allocate (ArrayType, size) { + if (ArrayType.allocUnsafe) { + return ArrayType.allocUnsafe(size); + } + return new ArrayType(size); + }; + BN.prototype.toArrayLike = function toArrayLike (ArrayType, endian, length) { + this._strip(); + var byteLength = this.byteLength(); var reqLength = length || Math.max(1, byteLength); assert(byteLength <= reqLength, 'byte array longer than desired length'); assert(reqLength > 0, 'Requested array length <= 0'); - this._strip(); - var littleEndian = endian === 'le'; var res = allocate(ArrayType, reqLength); + var postfix = endian === 'le' ? 'LE' : 'BE'; + this['_toArrayLike' + postfix](res, byteLength); + return res; + }; + + BN.prototype._toArrayLikeLE = function _toArrayLikeLE (res, byteLength) { + var position = 0; + var carry = 0; + + for (var i = 0, shift = 0; i < this.length; i++) { + var word = (this.words[i] << shift) | carry; - var b, i; - var q = this.clone(); - if (!littleEndian) { - // Assume big-endian - for (i = 0; i < reqLength - byteLength; i++) { - res[i] = 0; + res[position++] = word & 0xff; + if (position < res.length) { + res[position++] = (word >> 8) & 0xff; + } + if (position < res.length) { + res[position++] = (word >> 16) & 0xff; } - for (i = 0; !q.isZero(); i++) { - b = q.andln(0xff); - q.iushrn(8); + if (shift === 6) { + if (position < res.length) { + res[position++] = (word >> 24) & 0xff; + } + carry = 0; + shift = 0; + } else { + carry = word >>> 24; + shift += 2; + } + } + + if (position < res.length) { + res[position++] = carry; - res[reqLength - i - 1] = b; + while (position < res.length) { + res[position++] = 0; } - } else { - for (i = 0; !q.isZero(); i++) { - b = q.andln(0xff); - q.iushrn(8); + } + }; - res[i] = b; + BN.prototype._toArrayLikeBE = function _toArrayLikeBE (res, byteLength) { + var position = res.length - 1; + var carry = 0; + + for (var i = 0, shift = 0; i < this.length; i++) { + var word = (this.words[i] << shift) | carry; + + res[position--] = word & 0xff; + if (position >= 0) { + res[position--] = (word >> 8) & 0xff; + } + if (position >= 0) { + res[position--] = (word >> 16) & 0xff; } - for (; i < reqLength; i++) { - res[i] = 0; + if (shift === 6) { + if (position >= 0) { + res[position--] = (word >> 24) & 0xff; + } + carry = 0; + shift = 0; + } else { + carry = word >>> 24; + shift += 2; } } - return res; - }; + if (position >= 0) { + res[position--] = carry; - var allocate = function allocate (ArrayType, size) { - if (ArrayType.allocUnsafe) { - return ArrayType.allocUnsafe(size); + while (position >= 0) { + res[position--] = 0; + } } - return new ArrayType(size); }; if (Math.clz32) { diff --git a/test/utils-test.js b/test/utils-test.js index 7b45950..3f63cd5 100644 --- a/test/utils-test.js +++ b/test/utils-test.js @@ -144,8 +144,16 @@ describe('BN.js/Utils', function () { describe('.toBuffer', function () { it('should return proper Buffer', function () { var n = new BN(0x123456); - assert.deepEqual(n.toBuffer('be', 5).toString('hex'), '0000123456'); + assert.equal(n.toBuffer('be', 5).toString('hex'), '0000123456'); assert.deepEqual(n.toBuffer('le', 5).toString('hex'), '5634120000'); + + var s = '211e1566be78319bb949470577c2d4ac7e800a90d5104379478d8039451a8efe'; + for (var i = 1; i <= s.length; i++) { + var slice = (i % 2 === 0 ? '' : '0') + s.slice(0, i); + var bn = new BN(slice, 16); + assert.equal(bn.toBuffer('be').toString('hex'), slice); + assert.equal(bn.toBuffer('le').toString('hex'), Buffer.from(slice, 'hex').reverse().toString('hex')); + } }); });