From 064d031dc59b76c35e2d21578723c6aed9286c66 Mon Sep 17 00:00:00 2001 From: Snowflyt Date: Mon, 11 Nov 2024 11:37:02 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20test(benchmark):=20Add=20benchma?= =?UTF-8?q?rking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 51 ++++++++++++++++++++++++++++++++++++ package.json | 5 +++- src/fib.bench.ts | 64 +++++++++++++++++++++++++++++++++++++++++++++ tsconfig.build.json | 2 +- 4 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 src/fib.bench.ts diff --git a/package-lock.json b/package-lock.json index 754d9e8..f5802d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@vitest/coverage-v8": "^2.1.4", "@vitest/ui": "^2.1.4", "cpy-cli": "^5.0.0", + "effect": "^3.10.13", "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.6.3", @@ -2901,6 +2902,16 @@ "dev": true, "license": "MIT" }, + "node_modules/effect": { + "version": "3.10.13", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.10.13.tgz", + "integrity": "sha512-7L8BGZldtMpXx5mw5MRTsnW6j4zpa+K6Rn2pU5CJ4LyH9jm2Hopfgaq3YurkTmv5HWpCQnhqCkMhbaSG3Nporw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-check": "^3.21.0" + } + }, "node_modules/emoji-regex": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", @@ -3779,6 +3790,29 @@ "node": ">=12.0.0" } }, + "node_modules/fast-check": { + "version": "3.23.1", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.1.tgz", + "integrity": "sha512-u/MudsoQEgBUZgR5N1v87vEgybeVYus9VnDVaIkxkkGP2jt54naghQ3PCQHJiogS8U/GavZCUPFfx3Xkp+NaHw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6220,6 +6254,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/queue-lit": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", diff --git a/package.json b/package.json index a56794f..29bd1ee 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,10 @@ "module": "./index.js", "types": "./index.d.ts", "scripts": { + "bench": "vitest bench --run", + "bench:watch": "vitest bench", "prebuild": "npm run clean", - "build": "tsc --emitDeclarationOnly -p tsconfig.build.json && sucrase src -d dist --transforms typescript && rimraf -g dist/*.{spec,proof}.js dist/types.{d.js,d.ts} && cpy src/**/*.{js,d.ts} dist && tsc-alias -p tsconfig.build.json && replace-in-file \"/^\\s*\\/\\/ eslint-disable-next-line .+$/mg\" \"\" dist/**/*.js --isRegex && replace-in-file \"/^\\s*\\/\\/ @ts-.+$/mg\" \"\" dist/**/*.js --isRegex && prettier --log-level=silent --print-width 80 --write dist/**/* --ignore-path !dist/**/* && cpy package.json dist && json -I -f dist/package.json -e \"delete this.private; delete this.scripts; delete this.devDependencies\" && cpy README.md dist && cpy screenshot.svg dist && cpy LICENSE dist", + "build": "tsc --emitDeclarationOnly -p tsconfig.build.json && sucrase src -d dist --transforms typescript && rimraf -g dist/*.{proof,spec,bench}.js dist/types.{d.js,d.ts} && cpy src/**/*.{js,d.ts} dist && tsc-alias -p tsconfig.build.json && replace-in-file \"/^\\s*\\/\\/ eslint-disable-next-line .+$/mg\" \"\" dist/**/*.js --isRegex && replace-in-file \"/^\\s*\\/\\/ @ts-.+$/mg\" \"\" dist/**/*.js --isRegex && prettier --log-level=silent --print-width 80 --write dist/**/* --ignore-path !dist/**/* && cpy package.json dist && json -I -f dist/package.json -e \"delete this.private; delete this.scripts; delete this.devDependencies\" && cpy README.md dist && cpy screenshot.svg dist && cpy LICENSE dist", "clean": "rimraf dist", "format": "prettier --no-error-on-unmatched-pattern --write **/*.{js,ts,json,md} *.{cjs,mjs,cts,mts}", "lint": "eslint **/*.{js,ts} *.{cjs,mjs,cts,mts} --no-error-on-unmatched-pattern --report-unused-disable-directives-severity error --max-warnings 0", @@ -50,6 +52,7 @@ "@vitest/coverage-v8": "^2.1.4", "@vitest/ui": "^2.1.4", "cpy-cli": "^5.0.0", + "effect": "^3.10.13", "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.6.3", diff --git a/src/fib.bench.ts b/src/fib.bench.ts new file mode 100644 index 0000000..f137c05 --- /dev/null +++ b/src/fib.bench.ts @@ -0,0 +1,64 @@ +/** + * Benchmark for overhead of computationally expensive functions. + * + * Currently the effected version is around 100x (`fibPipe`) to 200x (`fibGen`) slower than the + * non-effected version. + * + * Such overhead should be acceptable for most use-cases, as real-world applications are unlikely to + * execute computationally expensive logic in an effected function. + * + * It is worth noting that tinyeffect’s version using generator syntax is around 20% faster than + * Effect’s version using `Effect.gen`, but the one using pipeline syntax is around 30%~120% slower + * than Effect’s version, which is quite surprising. A further investigation is needed to find out + * why Effect’s version is faster than tinyeffect’s version using pipeline syntax. + */ + +import { Effect } from "effect"; +import { bench, describe } from "vitest"; + +import { Effected, effected } from "."; + +const fib = (n: number): number => { + if (n <= 1) return n; + return fib(n - 1) + fib(n - 2); +}; + +const fibGen = (n: number): Effected => + effected(function* () { + if (n <= 1) return n; + return (yield* fibGen(n - 1)) + (yield* fibGen(n - 2)); + }); + +const fibPipe = (n: number): Effected => { + if (n <= 1) return Effected.of(n); + return fibPipe(n - 1).map((a) => fibPipe(n - 2).map((b) => a + b)); +}; + +const fibEGen = (n: number): Effect.Effect => + Effect.gen(function* () { + if (n <= 1) return n; + return (yield* fibEGen(n - 1)) + (yield* fibEGen(n - 2)); + }); + +const fibEPipe = (n: number): Effect.Effect => { + if (n <= 1) return Effect.succeed(n); + return fibEPipe(n - 1).pipe( + Effect.flatMap((a) => fibEPipe(n - 2).pipe(Effect.map((b) => a + b))), + ); +}; + +describe("fib(20)", () => { + bench("fib(20)", () => void fib(20)); + bench("fibGen(20)", () => void fibGen(20).runSync()); + bench("fibPipe(20)", () => void fibPipe(20).runSync()); + bench("fibEGen(20)", () => void Effect.runSync(fibEGen(20))); + bench("fibEPipe(20)", () => void Effect.runSync(fibEPipe(20))); +}); + +describe("fib(30)", () => { + bench("fib(30)", () => void fib(30)); + bench("fibGen(30)", () => void fibGen(30).runSync()); + bench("fibPipe(30)", () => void fibPipe(30).runSync()); + bench("fibEGen(30)", () => void Effect.runSync(fibEGen(30))); + bench("fibEPipe(30)", () => void Effect.runSync(fibEPipe(30))); +}); diff --git a/tsconfig.build.json b/tsconfig.build.json index 732d727..d36de9b 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -6,7 +6,7 @@ "outDir": "dist" }, "include": ["src"], - "exclude": ["src/**/*.spec.ts", "src/**/*.proof.ts"], + "exclude": ["src/**/*.proof.ts", "src/**/*.spec.ts", "src/**/*.bench.ts"], "tsc-alias": { "resolveFullPaths": true }