_benchmark/index.js
/* eslint-disable new-cap */
const ArgumentParser = require('argparse').ArgumentParser;
const benchmark = require('benchmark');
const crypto = require('crypto');
const XorShift128Plus = require('xorshift.js').XorShift128Plus;
const {ZZ, DEFAULT_DISPLAY_BASE, DEFAULT_REPRESENTATION_BASE} = require('..');
const bn = require('bn.js');
let bignum;
try {
bignum = require('bignum');
} catch (error) {
console.log('Load bignum error: ' + error.message.split('\n')[0]);
}
const sjcl = require('eccjs').sjcl.bn;
const bigi = require('bigi');
const BigInteger = require('js-big-integer').BigInteger;
const JSBI = require('jsbi');
const SilentMattBigInteger = require('biginteger').BigInteger;
const parser = new ArgumentParser();
parser.addArgument(['-s', '--seed'], {
defaultValue: process.env.SEED || crypto.randomBytes(16).toString('hex'),
});
parser.addArgument(['-l', '--libs'], {defaultValue: '.'});
parser.addArgument(['-b', '--benches'], {defaultValue: '.'});
parser.addArgument(['-f', '--fast'], {action: 'storeTrue'});
const args = parser.parseArgs();
const seed = args.seed;
const filter = new RegExp(args.libs, 'i');
const re = new RegExp(args.benches, 'i');
const fast = args.fast;
const benchmarks = [];
console.log('args:', args);
console.log('DEFAULT_DISPLAY_BASE:', DEFAULT_DISPLAY_BASE);
console.log('DEFAULT_REPRESENTATION_BASE:', DEFAULT_REPRESENTATION_BASE);
const prng = new XorShift128Plus(seed);
const NFIXTURES = 25;
const fixtures = [];
let findex = 0;
function findexRefresh() {
if (++findex === fixtures.length) findex = 0;
}
const k256 = 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f';
function add(op, ...args) {
const key = op + '-' + args.join('-');
benchmarks.push({
name: key,
start() {
const suite = new benchmark.Suite();
console.log('Benchmarking: ' + key);
for (const name of Object.keys(fns)) {
if (!filter.test(name)) continue;
if (name === 'BigInt' && typeof BigInt === 'undefined') continue;
if (name === 'bignum' && bignum === undefined) continue;
const opFn = fns[name][op];
if (!opFn) continue;
if (!(opFn instanceof Function))
throw new Error(`opFN is not a function: ${opFn}`);
const fixture = fixtures[findex][name];
if (args.length === 1) {
const x = fixture.args[args[0]];
const outs = fixture.outs;
const testFn = () => {
outs[key] = opFn(x);
};
suite.add(name + '#' + key, testFn, {
onStart: findexRefresh,
onCycle: findexRefresh,
});
} else if (args.length === 2) {
const a = fixture.args[args[0]];
const b = fixture.args[args[1]];
const outs = fixture.outs;
const testFn = () => {
outs[key] = opFn(a, b);
};
suite.add(name + '#' + key, testFn, {
onStart: findexRefresh,
onCycle: findexRefresh,
});
} else throw new Error('Too many args.');
}
suite
.on('cycle', (event) => {
console.log(String(event.target));
})
.on('complete', function () {
console.log('------------------------');
console.log('Fastest is ' + this.find('fastest').name);
})
.run();
console.log('========================');
},
});
}
function start() {
for (const b of benchmarks.filter((b) => {
return re.test(b.name);
})) {
b.start();
}
}
if (fast) {
console.log('Running in fast mode...');
benchmark.options.minTime = 0.3;
benchmark.options.maxTime = 1;
benchmark.options.minSamples = 3;
} else {
benchmark.options.minTime = 3;
benchmark.options.minSamples = 10;
}
const fns = {
BigInt: {
from10: (s) => BigInt(s),
from16: (s) => BigInt('0x' + s),
to10: (x) => x.toString(10),
to16: (x) => x.toString(16),
add: (a, b) => a + b,
sub: (a, b) => a - b,
mul: (a, b) => a * b,
sqr: (x) => x ** 2n,
pow: (a, b) => a ** b,
div: (a, b) => a / b,
mod: (a, b) => {
const remainder = a % b;
return remainder < 0 ? remainder + b : remainder;
},
toRed: (x) => x % k256_BigInt,
fromRed: (x) => x,
sqrm: (x) => x ** 2 % k256_BigInt,
powm: (a, b) => a ** b % k256_BigInt,
},
'bn.js': {
from10: (s) => new bn(s, 10),
from16: (s) => new bn(s, 16),
to10: (x) => x.toString(10),
to16: (x) => x.toString(16),
add: (a, b) => a.add(b),
sub: (a, b) => a.sub(b),
mul: (a, b) => a.mul(b),
sqr: (x) => x.mul(x),
pow: (a, b) => a.pow(b),
div: (a, b) => a.div(b),
mod: (a, b) => a.mod(b),
toRed: (x) => x.toRed(bn.red('k256')),
fromRed: (x) => x.fromRed(),
sqrm: (x) => x.redSqr(),
powm: (a, b) => a.redPow(b),
invm: (x) => x.redInvm(),
gcd: (a, b) => a.gcd(b),
egcd: (a, b) => a.egcd(b),
},
'@aureooms/js-integer': {
from10: (s) => ZZ.from(s, 10),
from16: (s) => ZZ.from(s, 16),
to10: (x) => x.toString(10),
to16: (x) => x.toString(16),
add: (a, b) => a.add(b),
sub: (a, b) => a.sub(b),
mul: (a, b) => a.mul(b),
sqr: (x) => x.square(),
pow: (a, b) => a.pow(b),
div: (a, b) => a.div(b),
mod: (a, b) => a.mod(b),
gcd: (a, b) => a.gcd(b),
egcd: (a, b) => a.egcd(b),
},
jsbi: {
from10: (s) => JSBI.BigInt(s),
from16: (s) => JSBI.BigInt('0x' + s),
to10: (x) => x.toString(10),
to16: (x) => x.toString(16),
add: (a, b) => JSBI.add(a, b),
sub: (a, b) => JSBI.subtract(a, b),
mul: (a, b) => JSBI.multiply(a, b),
sqr: (x) => JSBI.multiply(x, x),
pow: (a, b) => JSBI.exponentiate(a, b),
div: (a, b) => JSBI.divide(a, b),
mod: (a, b) => {
const remainder = JSBI.remainder(a, b);
return JSBI.lessThan(remainder, JSBI.BigInt(0))
? JSBI.add(remainder, b)
: remainder;
},
},
yaffle: {
from10: (s) => BigInteger.BigInt(s),
from16: (s) => BigInteger.BigInt('0x' + s),
to10: (x) => x.toString(10),
to16: (x) => x.toString(16),
add: (a, b) => BigInteger.add(a, b),
sub: (a, b) => BigInteger.subtract(a, b),
mul: (a, b) => BigInteger.multiply(a, b),
sqr: (x) => BigInteger.multiply(x, x),
pow: (a, b) => BigInteger.exponentiate(a, b),
div: (a, b) => BigInteger.divide(a, b),
mod: (a, b) => {
const remainder = BigInteger.remainder(a, b);
return BigInteger.lessThan(remainder, BigInteger.BigInt(0))
? BigInteger.add(remainder, b)
: remainder;
},
},
bigi: {
from10: (s) => new bigi(s, 10),
from16: (s) => new bigi(s, 16),
to10: (x) => x.toString(10),
to16: (x) => x.toString(16),
add: (a, b) => a.add(b),
sub: (a, b) => a.subtract(b),
mul: (a, b) => a.multiply(b),
sqr: (x) => x.square(),
pow: (a, b) => a.pow(b),
div: (a, b) => a.divide(b),
mod: (a, b) => a.remainder(b),
gcd: (a, b) => a.gcd(b),
},
'silentmatt-biginteger': {
from10: (s) => SilentMattBigInteger.parse(s, 10),
from16: (s) => SilentMattBigInteger.parse(s, 16),
to10: (x) => x.toString(10),
to16: (x) => x.toString(16).toLowerCase(),
add: (a, b) => a.add(b),
sub: (a, b) => a.subtract(b),
mul: (a, b) => a.multiply(b),
sqr: (x) => x.square(x),
pow: (a, b) => a.pow(b),
div: (a, b) => a.quotient(b),
mod: (a, b) => {
const remainder = a.remainder(b);
return remainder.isNegative() ? remainder.add(b) : remainder;
},
},
bignum: {
from10: (s) => new bignum(s, 10),
from16: (s) => new bignum(s, 16),
to10: (x) => x.toString(10),
to16: (x) => x.toString(16).replace(/^0+/, ''),
add: (a, b) => a.add(b),
sub: (a, b) => a.sub(b),
mul: (a, b) => a.mul(b),
sqr: (x) => x.mul(x),
pow: (a, b) => a.pow(b),
div: (a, b) => a.div(b),
mod: (a, b) => a.mod(b),
fromRed: (x) => x,
toRed: (x) => x.mod(k256_bignum),
powm: (a, b) => a.powm(b, k256_bignum),
sqrm: (x) => x.pown(2, k256_bignum),
invm: (x) => x.invertm(k256_bignum),
},
sjcl: {
from10: undefined,
from16: (s) => new sjcl(s),
to10: undefined,
to16: (x) => x.toString().slice(2),
add: (a, b) => a.add(b),
sub: (a, b) => a.sub(b),
mul: (a, b) => a.mul(b),
sqr: (x) => x.mul(x),
pow: (a, b) => a.power(b),
fromRed: (x) => x,
toRed: (x) => new sjcl.prime.p256k(x),
invm: (x) => x.inverseMod(k256_sjcl),
sqrm: (x) => x.square().fullReduce(),
},
};
const k256_BigInt = fns.BigInt.from16(k256);
const k256_bignum = fns.bignum.from16(k256);
const k256_sjcl = fns.sjcl.from16(k256);
fns.BigInt.k256 = k256_BigInt;
fns.bignum.k256 = k256_bignum;
fns.sjcl.k256 = k256_sjcl;
fns['bn.js'].k256 = 'k256';
function newFixture() {
const fixture = {};
const _a32 = prng.randomBytes(32).toString('hex');
const _b32 = prng.randomBytes(32).toString('hex');
const _a64 = prng.randomBytes(64).toString('hex');
const _b64 = prng.randomBytes(64).toString('hex');
const _a128 = prng.randomBytes(128).toString('hex');
const _b128 = prng.randomBytes(128).toString('hex');
const _a256 = prng.randomBytes(256).toString('hex');
const _b256 = prng.randomBytes(256).toString('hex');
const _a512 = prng.randomBytes(512).toString('hex');
const _b512 = prng.randomBytes(512).toString('hex');
const _a768 = prng.randomBytes(768).toString('hex');
const _b768 = prng.randomBytes(768).toString('hex');
const _a1024 = prng.randomBytes(1024).toString('hex');
const _b1024 = prng.randomBytes(1024).toString('hex');
const _a2048 = prng.randomBytes(2048).toString('hex');
const _b2048 = prng.randomBytes(2048).toString('hex');
const _a4096 = prng.randomBytes(4096).toString('hex');
const _b4096 = prng.randomBytes(4096).toString('hex');
const _a8192 = prng.randomBytes(8192).toString('hex');
const _b8192 = prng.randomBytes(8192).toString('hex');
const a10base = new bn(_a32, 16).toString(10);
const a16base = new bn(_b32, 16).toString(16);
const init = (fn) => {
const a32 = fn.from16(_a32);
const b32 = fn.from16(_b32);
const a64 = fn.from16(_a64);
const b64 = fn.from16(_b64);
const a128 = fn.from16(_a128);
const b128 = fn.from16(_b128);
const a256 = fn.from16(_a256);
const b256 = fn.from16(_b256);
const a512 = fn.from16(_a512);
const b512 = fn.from16(_b512);
const a768 = fn.from16(_a768);
const b768 = fn.from16(_b768);
const a1024 = fn.from16(_a1024);
const b1024 = fn.from16(_b1024);
const a2048 = fn.from16(_a2048);
const b2048 = fn.from16(_b2048);
const a4096 = fn.from16(_a4096);
const b4096 = fn.from16(_b4096);
const a8192 = fn.from16(_a8192);
const b8192 = fn.from16(_b8192);
const x = fn.from16('2adbeef');
const as1 = fn.add(fn.sqr(a32), x);
const am1 = fn.toRed && fn.toRed(a32);
const pow1 = fn.fromRed && fn.fromRed(am1);
return {
a32,
b32,
a64,
b64,
a128,
b128,
a256,
b256,
a512,
b512,
a768,
b768,
_a32,
_b32,
_a64,
_b64,
_a128,
_b128,
_a256,
_b256,
_a512,
_b512,
_a768,
_b768,
a1024,
b1024,
a2048,
b2048,
a4096,
b4096,
a8192,
b8192,
_a1024,
_b1024,
_a2048,
_b2048,
_a4096,
_b4096,
_a8192,
_b8192,
as1,
am1,
pow1,
a10base,
a16base,
};
};
for (const name of Object.keys(fns)) {
if (!filter.test(name)) continue;
fixture[name] = {};
fixture[name].args = init(fns[name]);
fixture[name].outs = {};
}
return fixture;
}
while (fixtures.length < NFIXTURES) fixtures.push(newFixture());
add('from10', 'a10base');
add('from16', 'a16base');
const ALL = [
'a32',
'a64',
'a128',
'a256',
'a512',
'a768',
'a1024',
'a2048',
'a4096',
'a8192',
];
for (const x of ALL) add('from16', '_' + x);
for (const x of ALL) add('to16', x);
const ALL_OPS = [
['a32', 'b32'],
['a64', 'b64'],
['a128', 'b128'],
['a256', 'b256'],
['a512', 'b512'],
['a768', 'b768'],
['a1024', 'b1024'],
['a2048', 'b2048'],
['a4096', 'b4096'],
['a8192', 'b8192'],
];
for (const [a, b] of ALL_OPS) add('add', a, b);
for (const [a, b] of ALL_OPS) add('sub', a, b);
for (const [a, b] of ALL_OPS) add('mul', a, b);
for (const [a, b] of ALL_OPS) add('div', a, b);
add('sqr', 'a32');
add('div', 'as1', 'a32');
add('mod', 'as1', 'a32');
add('sqrm', 'am1');
add('powm', 'am1', 'pow1');
add('invm', 'am1');
add('gcd', 'a32', 'b32');
add('egcd', 'a32', 'b32');
start();
// Forces ALL computations ?
const results = new Array(NFIXTURES);
for (let i = 0; i < NFIXTURES; ++i) {
results[i] = {};
for (const name of Object.keys(fns)) {
if (!filter.test(name)) continue;
const fn = fns[name];
const fixture = fixtures[i][name];
for (const key of Object.keys(fixture.outs)) {
results[i][key] = results[i][key] || {};
const result = fixture.outs[key];
const str =
result.constructor.prototype === String.prototype
? result
: fn.to16(result).toLowerCase();
results[i][key][name] = str;
}
}
for (const key of Object.keys(results[i])) {
const theseResults = results[i][key];
const distinctResults = new Set(Object.values(theseResults));
if (distinctResults.size === 1) {
console.error(i, key, JSON.stringify(Object.keys(theseResults)), 'OK');
} else {
console.error('DIFFERENT OUTPUTS for', i, key);
console.error(distinctResults);
console.error(i, key, theseResults);
console.error(fixtures[i]);
}
}
}