diff --git a/benchmark/benchmarks.js b/benchmark/benchmarks.js index 4ecd0a9..e125478 100644 --- a/benchmark/benchmarks.js +++ b/benchmark/benchmarks.js @@ -1,64 +1,56 @@ -var base62 = require('../lib/ascii'); -var base62custom = require('../lib/custom'); +"use strict"; -var encode = base62.encode; -var decode = base62.decode; -var encodeCustom = base62custom.encode; -var decodeCustom = base62custom.decode; -var indexCharset = base62custom.indexCharset; +var base62 = require("../lib/ascii"); +var base62custom = require("../lib/custom"); -var intResult, strResult, now, delta, i; -var charset = indexCharset('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'); +var charset = base62custom.indexCharset("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); -function performanceNow() { - var t = process.hrtime(); - return t[0] * 1000 + t[1] / 1000000; -} - -// decode with default charset (ASCII) - -now = performanceNow(); -for (intResult = 0, i = 0; i < 1000000; i++) { - intResult += decode('00thing'); -} - -delta = performanceNow() - now; -console.log('|', 'decoding with default charset (1000000x)', '|', - intResult === 432635954000000 ? 'correct' : 'incorrect', '|', - delta.toFixed(2), 'ms', '|'); - -// encode with default charset (ASCII) - -now = performanceNow(); -for (strResult = '', i = 0; i < 1000000; i++) { - strResult = encode(i); -} - -delta = performanceNow() - now; -console.log('|', 'encoding with default charset (1000000x)', '|', - strResult === '4c91' ? 'correct' : 'incorrect', '|', - delta.toFixed(2), 'ms', '|'); - -// decode with custom charset - -now = performanceNow(); -for (intResult = 0, i = 0; i < 1000000; i++) { - intResult += decodeCustom('00thing', charset); -} - -delta = performanceNow() - now; -console.log('|', 'decoding with custom charset (1000000x)', '|', - intResult === 823118800000000 ? 'correct' : 'incorrect', '|', - delta.toFixed(2), 'ms', '|'); - -// encode with custom charset +function bench(name, iterations, fn) { + // warmup + for (var w = 0; w < 1000; w++) { fn(w); } -now = performanceNow(); -for (strResult = '', i = 0; i < 1000000; i++) { - strResult = encodeCustom(i, charset); + var start = process.hrtime.bigint(); + for (var i = 0; i < iterations; i++) { + fn(i); + } + var ns = process.hrtime.bigint() - start; + var ms = Number(ns) / 1e6; + var opsPerSec = Math.round(iterations / (ms / 1000)); + console.log(" " + name.padEnd(30) + ms.toFixed(2).padStart(10) + "ms" + opsPerSec.toLocaleString().padStart(16) + " ops/sec"); } -delta = performanceNow() - now; -console.log('|', 'encoding with custom charset (1000000x)', '|', - strResult === '4C91' ? 'correct' : 'incorrect', '|', - delta.toFixed(2), 'ms', '|'); +var n = 5000000; + +console.log("ASCII charset (" + n.toLocaleString() + " iterations)\n"); + +console.log(" Small numbers (0 to " + n.toLocaleString() + "):"); +bench("encode", n, function(i) { base62.encode(i); }); +bench("decode", n, function() { base62.decode("g7"); }); +bench("encode bigint", n, function(i) { base62.encode(BigInt(i)); }); +bench("decode bigint", n, function() { base62.decode("g7", { bigint: true }); }); + +console.log(" Large numbers (~10^13):"); +bench("encode", n, function(i) { base62.encode(i + 10000000000000); }); +bench("decode", n, function() { base62.decode("2Q3rKTOF"); }); +bench("encode bigint", n, function(i) { base62.encode(BigInt(i) + 10000000000000n); }); +bench("decode bigint", n, function() { base62.decode("2Q3rKTOF", { bigint: true }); }); + +console.log(" Very large bigints (~10^30):"); +var bigOffset = 123456789012345678901234567890n; +bench("encode bigint", n, function(i) { base62.encode(BigInt(i) + bigOffset); }); +bench("decode bigint", n, function() { base62.decode("3oPBaJPMWjmDE4RUeJ", { bigint: true }); }); + +console.log(); +console.log("Custom charset (" + n.toLocaleString() + " iterations)\n"); + +console.log(" Small numbers (0 to " + n.toLocaleString() + "):"); +bench("encode", n, function(i) { base62custom.encode(i, charset); }); +bench("decode", n, function() { base62custom.decode("G2", charset); }); +bench("encode bigint", n, function(i) { base62custom.encode(BigInt(i), charset); }); +bench("decode bigint", n, function() { base62custom.decode("G2", charset, { bigint: true }); }); + +console.log(" Large numbers (~10^13):"); +bench("encode", n, function(i) { base62custom.encode(i + 10000000000000, charset); }); +bench("decode", n, function() { base62custom.decode("7c3RKTOQ", charset); }); +bench("encode bigint", n, function(i) { base62custom.encode(BigInt(i) + 10000000000000n, charset); }); +bench("decode bigint", n, function() { base62custom.decode("7c3RKTOQ", charset, { bigint: true }); }); diff --git a/benchmark/benchmarks_legacy.js b/benchmark/benchmarks_legacy.js deleted file mode 100644 index 94b8343..0000000 --- a/benchmark/benchmarks_legacy.js +++ /dev/null @@ -1,65 +0,0 @@ -var base62 = require("../"); - -var now = 0, - deltaTime = 0, - i = 0, - intResult = 0, - strResult = 0; - -function performanceNow(){ - var t = process.hrtime(); - - return t[0] * 1000 + t[1] / 1000000; -} - -console.log('running legacy benchmarks'); - -//decode with default charset - -now = performanceNow(); - -for (intResult = 0, i = 0; i < 1000000; i++) { - intResult += base62.decode('00thing'); -} - -deltaTime = performanceNow() - now; - -console.log('|', 'decoding with default charset (1000000x)', '|', intResult === 432635954000000 ? 'correct' : 'incorrect', '|', deltaTime.toFixed(2), 'ms', '|'); - -//encode with default charset - -now = performanceNow(); - -for (strResult = '', i = 0; i < 1000000; i++) { - strResult = base62.encode(i); -} - -deltaTime = performanceNow() - now; - -console.log('|', 'encoding with default charset (1000000x)', '|', strResult === '4c91' ? 'correct' : 'incorrect', '|', deltaTime.toFixed(2), 'ms', '|'); - -//decode with custom charset - -base62.setCharacterSet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'); - -now = performanceNow(); - -for (intResult = 0, i = 0; i < 1000000; i++) { - intResult += base62.decode('00thing'); -} - -deltaTime = performanceNow() - now; - -console.log('|', 'decoding with custom charset (1000000x)', '|', intResult === 823118800000000 ? 'correct' : 'incorrect', '|', deltaTime.toFixed(2), 'ms', '|'); - -//encode with custom charset - -now = performanceNow(); - -for (strResult = '', i = 0; i < 1000000; i++) { - strResult = base62.encode(i); -} - -deltaTime = performanceNow() - now; - -console.log('|', 'encoding with custom charset (1000000x)', '|', strResult === '4C91' ? 'correct' : 'incorrect', '|', deltaTime.toFixed(2), 'ms', '|'); diff --git a/lib/ascii.js b/lib/ascii.js index ebdd61c..2a09201 100644 --- a/lib/ascii.js +++ b/lib/ascii.js @@ -1,6 +1,12 @@ "use strict"; -var CHARSET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""); +var CHARSET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +// Lookup table: charCode -> value (e.g. '0'=48 -> 0, 'a'=97 -> 10, 'A'=65 -> 36) +var CHAR_TO_VAL = new Uint8Array(123); +for (var ci = 0; ci < 62; ci++) { + CHAR_TO_VAL[CHARSET[ci].charCodeAt(0)] = ci; +} // NB: does not validate input exports.encode = function encode(int) { @@ -10,8 +16,9 @@ exports.encode = function encode(int) { } var res = ""; while (int > 0n) { - res = CHARSET[Number(int % 62n)] + res; - int = int / 62n; + var rem = int % 62n; + res = CHARSET[Number(rem)] + res; + int = (int - rem) / 62n; } return res; } @@ -22,8 +29,9 @@ exports.encode = function encode(int) { var res = ""; while (int > 0) { - res = CHARSET[int % 62] + res; - int = Math.floor(int / 62); + var rem = int % 62; + res = CHARSET[rem] + res; + int = (int - rem) / 62; } return res; }; @@ -36,30 +44,14 @@ exports.decode = function decode(str, options) { if (useBigInt) { var res = 0n; for (i = 0; i < length; i++) { - char = str.charCodeAt(i); - if (char < 58) { - char = char - 48; - } else if (char < 91) { - char = char - 29; - } else { - char = char - 87; - } - res += BigInt(char) * 62n ** BigInt(length - i - 1); + res = res * 62n + BigInt(CHAR_TO_VAL[str.charCodeAt(i)]); } return res; } var res = 0; for (i = 0; i < length; i++) { - char = str.charCodeAt(i); - if (char < 58) { // 0-9 - char = char - 48; - } else if (char < 91) { // A-Z - char = char - 29; - } else { // a-z - char = char - 87; - } - res += char * Math.pow(62, length - i - 1); + res = res * 62 + CHAR_TO_VAL[str.charCodeAt(i)]; } return res; }; diff --git a/lib/custom.js b/lib/custom.js index b707762..33e547b 100644 --- a/lib/custom.js +++ b/lib/custom.js @@ -11,8 +11,9 @@ exports.encode = function encode(int, charset) { var res = "", max = BigInt(charset.length); while (int > 0n) { - res = byCode[Number(int % max)] + res; - int = int / max; + var rem = int % max; + res = byCode[Number(rem)] + res; + int = (int - rem) / max; } return res; } @@ -24,8 +25,9 @@ exports.encode = function encode(int, charset) { var res = "", max = charset.length; while (int > 0) { - res = byCode[int % max] + res; - int = Math.floor(int / max); + var rem = int % max; + res = byCode[rem] + res; + int = (int - rem) / max; } return res; }; @@ -41,7 +43,7 @@ exports.decode = function decode(str, charset, options) { bigMax = BigInt(max); for (i = 0; i < length; i++) { char = str[i]; - res += BigInt(byChar[char]) * bigMax ** BigInt(length - i - 1); + res = res * bigMax + BigInt(byChar[char]); } return res; } @@ -49,7 +51,7 @@ exports.decode = function decode(str, charset, options) { var res = 0; for (i = 0; i < length; i++) { char = str[i]; - res += byChar[char] * Math.pow(max, (length - i - 1)); + res = res * max + byChar[char]; } return res; }; diff --git a/package.json b/package.json index 7cb0932..7269eb6 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "scripts": { "test": "node --test test/*.js", - "benchmark": "node benchmark/benchmarks.js; echo; node benchmark/benchmarks_legacy.js" + "benchmark": "node benchmark/benchmarks.js" }, "devDependencies": {} }