Skip to content

Commit

Permalink
Improve performance of toArrayLike (indutny#222)
Browse files Browse the repository at this point in the history
* add toArray to benchmark

* optimize BN#toArrayLike

* split BN#toArrayLike

* fix bounds in toArrayLike*

* add more tests for toArrayLike

* save 1 op in toArrayLike*

(cherry picked from commit b28644b)
  • Loading branch information
fanatid authored and ivanshukhov committed Jul 31, 2020
1 parent 0a3148d commit e28cd3a
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 26 deletions.
6 changes: 6 additions & 0 deletions benchmarks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,4 +436,10 @@ add('bitLength', {
}
});

add('toArray', {
'bn.js': function () {
fixture.a1j.toArray();
}
});

start();
96 changes: 71 additions & 25 deletions lib/bn.js
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,16 @@
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();
if(this.byteLength() < 32 && length === undefined) {
length = 32
Expand All @@ -536,45 +545,82 @@
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) {
Expand Down
10 changes: 9 additions & 1 deletion test/utils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
}
});
});

Expand Down

0 comments on commit e28cd3a

Please sign in to comment.