From 4d53643bd39ad82b1b330ee8de7eb0bd2c371710 Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 6 Jul 2024 22:11:34 +0900 Subject: [PATCH] Add benchmarks and make sure Node is faster --- .github/workflows/ci.yml | 19 ++++++++--- benchmarks/main.js | 72 ++++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ src/cjs/index.cjs | 20 +++++++---- src/cjs/index.d.ts | 2 +- src/mjs/index.js | 16 ++++++--- ts_src/index.ts | 20 ++++++++--- 7 files changed, 131 insertions(+), 20 deletions(-) create mode 100644 benchmarks/main.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41f6be4..6ed6e57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,17 +13,28 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 14 + node-version: 20 registry-url: https://registry.npmjs.org/ - run: npm i - run: npm run unit + benchmarks: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 20 + registry-url: https://registry.npmjs.org/ + - run: npm i + - run: npm run benchmarks coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 14 + node-version: 20 registry-url: https://registry.npmjs.org/ - run: npm i - run: npm run coverage @@ -33,7 +44,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 14 + node-version: 20 registry-url: https://registry.npmjs.org/ - run: npm i - run: npm run gitdiff:ci @@ -43,7 +54,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 14 + node-version: 20 registry-url: https://registry.npmjs.org/ - run: npm i - run: npm run lint diff --git a/benchmarks/main.js b/benchmarks/main.js new file mode 100644 index 0000000..79e5ead --- /dev/null +++ b/benchmarks/main.js @@ -0,0 +1,72 @@ +import { Suite } from "bench-node"; +import * as nodeTools from "../src/mjs/index.js"; +import * as browserTools from "../src/mjs/browser.js"; + +const DUMMY_BUFFER = Uint8Array.from([ + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, + 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, + 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, + 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, + 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, + 0xbe, 0xef, +]); +const DUMMY_HEX = + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + +// Note: only include benchmarks for functions with differing implementations for Node and Browser +const BENCHMARKS = [ + [ + `fromHex`, + (library) => () => { + library.fromHex(DUMMY_HEX); + }, + ], + [ + `toHex`, + (library) => () => { + library.toHex(DUMMY_BUFFER); + }, + ], +]; + +const LIBRARIES = [ + ["Node ", nodeTools], + ["Browser", browserTools], +]; + +function setUpSuite(suite, platform, library, funcName, func) { + suite.add(`${platform} ${funcName}`, func(library)); +} + +async function main() { + const suite = new Suite(); + for (const [funcName, func] of BENCHMARKS) { + for (const [name, library] of LIBRARIES) { + setUpSuite(suite, name, library, funcName, func); + } + } + const results = await suite.run(); + + const nodeSlower = []; + + for (let i = 0; i < results.length; i += 2) { + const nodeResult = results[i]; + const browserResult = results[i + 1]; + if (nodeResult.opsSec < browserResult.opsSec) { + nodeSlower.push(BENCHMARKS[i / 2][0]); + } + } + if (nodeSlower.length > 0) { + throw new Error( + `\n*** Node was slower in the following:\n *** ${nodeSlower.join( + "\n *** " + )}` + ); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/package.json b/package.json index 241b102..cc7cda2 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "types": "src/cjs/index.d.ts", "type": "module", "scripts": { + "benchmarks": "node benchmarks/main.js", "build": "npm run clean && npm run build-ts && npm run convert-cjs && rm -f ./src/cjs/browser.d.ts", "build-ts": "tsc -p tsconfig.json && tsc -p tsconfig-cjs.json", "clean": "rm -rf ./src/* && rm -rf ./coverage && rm -f ./package-lock.json", @@ -50,6 +51,7 @@ "@types/node": "16.11.1", "@typescript-eslint/eslint-plugin": "5.0.0", "@typescript-eslint/parser": "5.0.0", + "bench-node": "0.0.1-beta.0", "eslint": "8.0.1", "eslint-config-prettier": "8.3.0", "eslint-plugin-prettier": "4.0.0", diff --git a/src/cjs/index.cjs b/src/cjs/index.cjs index 473f705..eda0d6c 100644 --- a/src/cjs/index.cjs +++ b/src/cjs/index.cjs @@ -1,10 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.compare = exports.fromHex = exports.toHex = exports.toUtf8 = void 0; -function toUtf8(bytes) { - return Buffer.from(bytes || []).toString(); -} -exports.toUtf8 = toUtf8; +exports.toUtf8 = exports.compare = exports.fromHex = exports.toHex = void 0; function toHex(bytes) { return Buffer.from(bytes || []).toString("hex"); } @@ -13,7 +9,19 @@ function fromHex(hexString) { return Uint8Array.from(Buffer.from(hexString || "", "hex")); } exports.fromHex = fromHex; +// Same behavior as Buffer.compare() function compare(v1, v2) { - return Buffer.from(v1).compare(Buffer.from(v2)); + const minLength = Math.min(v1.length, v2.length); + for (let i = 0; i < minLength; ++i) { + if (v1[i] !== v2[i]) { + return v1[i] < v2[i] ? -1 : 1; + } + } + return v1.length === v2.length ? 0 : v1.length > v2.length ? 1 : -1; } exports.compare = compare; +const DECODER = new TextDecoder(); +function toUtf8(bytes) { + return DECODER.decode(bytes); +} +exports.toUtf8 = toUtf8; diff --git a/src/cjs/index.d.ts b/src/cjs/index.d.ts index 2a1717a..84ddb86 100644 --- a/src/cjs/index.d.ts +++ b/src/cjs/index.d.ts @@ -1,5 +1,5 @@ -export declare function toUtf8(bytes: Uint8Array): string; export declare function toHex(bytes: Uint8Array): string; export declare function fromHex(hexString: string): Uint8Array; export declare type CompareResult = -1 | 0 | 1; export declare function compare(v1: Uint8Array, v2: Uint8Array): CompareResult; +export declare function toUtf8(bytes: Uint8Array): string; diff --git a/src/mjs/index.js b/src/mjs/index.js index 8d35bf4..a64a91a 100644 --- a/src/mjs/index.js +++ b/src/mjs/index.js @@ -1,12 +1,20 @@ -export function toUtf8(bytes) { - return Buffer.from(bytes || []).toString(); -} export function toHex(bytes) { return Buffer.from(bytes || []).toString("hex"); } export function fromHex(hexString) { return Uint8Array.from(Buffer.from(hexString || "", "hex")); } +// Same behavior as Buffer.compare() export function compare(v1, v2) { - return Buffer.from(v1).compare(Buffer.from(v2)); + const minLength = Math.min(v1.length, v2.length); + for (let i = 0; i < minLength; ++i) { + if (v1[i] !== v2[i]) { + return v1[i] < v2[i] ? -1 : 1; + } + } + return v1.length === v2.length ? 0 : v1.length > v2.length ? 1 : -1; +} +const DECODER = new TextDecoder(); +export function toUtf8(bytes) { + return DECODER.decode(bytes); } diff --git a/ts_src/index.ts b/ts_src/index.ts index deb373b..0ffc163 100644 --- a/ts_src/index.ts +++ b/ts_src/index.ts @@ -1,7 +1,3 @@ -export function toUtf8(bytes: Uint8Array): string { - return Buffer.from(bytes || []).toString(); -} - export function toHex(bytes: Uint8Array): string { return Buffer.from(bytes || []).toString("hex"); } @@ -10,7 +6,21 @@ export function fromHex(hexString: string): Uint8Array { return Uint8Array.from(Buffer.from(hexString || "", "hex")); } +// API that is copy-paste from browser + export type CompareResult = -1 | 0 | 1; +// Same behavior as Buffer.compare() export function compare(v1: Uint8Array, v2: Uint8Array): CompareResult { - return Buffer.from(v1).compare(Buffer.from(v2)) as CompareResult; + const minLength = Math.min(v1.length, v2.length); + for (let i = 0; i < minLength; ++i) { + if (v1[i] !== v2[i]) { + return v1[i] < v2[i] ? -1 : 1; + } + } + return v1.length === v2.length ? 0 : v1.length > v2.length ? 1 : -1; +} + +const DECODER = new TextDecoder(); +export function toUtf8(bytes: Uint8Array): string { + return DECODER.decode(bytes); }