From 2e6441a1d0067b1f87f0f1f08168803ffccd700f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Mon, 30 Sep 2024 14:56:21 +0200 Subject: [PATCH 1/7] fix: add accurate percentiles approximation computation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #72 Signed-off-by: Jérôme Benoit --- package.json | 8 ++- pnpm-lock.yaml | 143 +++++++++++++------------------------------------ src/task.ts | 11 ++-- src/utils.ts | 28 +++++++++- tsconfig.json | 2 +- 5 files changed, 76 insertions(+), 116 deletions(-) diff --git a/package.json b/package.json index 3a9d664..553e946 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,10 @@ "version": "2.9.0", "type": "module", "packageManager": "pnpm@8.4.0", + "volta": { + "node": "20.17.0", + "pnpm": "8.4.0" + }, "scripts": { "dev": "tsup --watch", "build": "tsup", @@ -29,8 +33,8 @@ "@size-limit/preset-small-lib": "^7.0.4", "@size-limit/time": "^7.0.8", "@types/node": "^18.7.13", - "@typescript-eslint/eslint-plugin": "^5.35.1", - "@typescript-eslint/parser": "^5.35.1", + "@typescript-eslint/eslint-plugin": "^5.59.1", + "@typescript-eslint/parser": "^5.59.1", "bumpp": "^8.2.0", "changelogithub": "^0.12.4", "clean-publish": "^3.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47c716e..a2048ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,10 +14,10 @@ importers: specifier: ^18.7.13 version: 18.16.2 '@typescript-eslint/eslint-plugin': - specifier: ^5.35.1 + specifier: ^5.59.1 version: 5.59.1(@typescript-eslint/parser@5.59.1)(eslint@8.39.0)(typescript@5.2.2) '@typescript-eslint/parser': - specifier: ^5.35.1 + specifier: ^5.59.1 version: 5.59.1(eslint@8.39.0)(typescript@5.2.2) bumpp: specifier: ^8.2.0 @@ -736,7 +736,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4 + debug: 4.3.6 espree: 9.5.1 globals: 13.20.0 ignore: 5.2.4 @@ -756,9 +756,10 @@ packages: /@humanwhocodes/config-array@0.11.8: resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead dependencies: '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 + debug: 4.3.6 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -771,6 +772,7 @@ packages: /@humanwhocodes/object-schema@1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + deprecated: Use @eslint/object-schema instead dev: true /@isaacs/cliui@8.0.2: @@ -811,10 +813,6 @@ packages: engines: {node: '>=6.0.0'} dev: true - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: true - /@jridgewell/sourcemap-codec@1.5.0: resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} dev: true @@ -1000,7 +998,7 @@ packages: resolution: {integrity: sha512-dNZafjM93Y+F+sfwTO5gTpsGXlnc/0Q+c2+62ViqP3gkMWvHEMSKkaEHgVJLcLg3i/g19GSIPziiKpgyne07Bw==} engines: {node: '>=8'} dependencies: - debug: 4.3.4 + debug: 4.3.6 transitivePeerDependencies: - supports-color dev: true @@ -1106,7 +1104,7 @@ packages: '@typescript-eslint/scope-manager': 5.59.1 '@typescript-eslint/type-utils': 5.59.1(eslint@8.39.0)(typescript@5.2.2) '@typescript-eslint/utils': 5.59.1(eslint@8.39.0)(typescript@5.2.2) - debug: 4.3.4 + debug: 4.3.6 eslint: 8.39.0 grapheme-splitter: 1.0.4 ignore: 5.2.4 @@ -1131,7 +1129,7 @@ packages: '@typescript-eslint/scope-manager': 5.59.1 '@typescript-eslint/types': 5.59.1 '@typescript-eslint/typescript-estree': 5.59.1(typescript@5.2.2) - debug: 4.3.4 + debug: 4.3.6 eslint: 8.39.0 typescript: 5.2.2 transitivePeerDependencies: @@ -1158,7 +1156,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 5.59.1(typescript@5.2.2) '@typescript-eslint/utils': 5.59.1(eslint@8.39.0)(typescript@5.2.2) - debug: 4.3.4 + debug: 4.3.6 eslint: 8.39.0 tsutils: 3.21.0(typescript@5.2.2) typescript: 5.2.2 @@ -1182,7 +1180,7 @@ packages: dependencies: '@typescript-eslint/types': 5.59.1 '@typescript-eslint/visitor-keys': 5.59.1 - debug: 4.3.4 + debug: 4.3.6 globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.0 @@ -1281,7 +1279,7 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: - debug: 4.3.4 + debug: 4.3.6 transitivePeerDependencies: - supports-color dev: true @@ -1424,13 +1422,6 @@ packages: balanced-match: 1.0.2 dev: true - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - dev: true - /braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1504,16 +1495,16 @@ packages: /c12@1.4.1: resolution: {integrity: sha512-0x7pWfLZpZsgtyotXtuepJc0rZYE0Aw8PwNAXs0jSG9zq6Sl5xmbWnFqfmRY01ieZLHNbvneSFm9/x88CvzAuw==} dependencies: - chokidar: 3.5.3 + chokidar: 3.6.0 defu: 6.1.2 dotenv: 16.0.3 giget: 1.1.2 jiti: 1.18.2 - mlly: 1.2.0 + mlly: 1.4.2 ohash: 1.1.2 - pathe: 1.1.0 + pathe: 1.1.1 perfect-debounce: 0.1.3 - pkg-types: 1.0.2 + pkg-types: 1.0.3 rc9: 2.1.0 transitivePeerDependencies: - supports-color @@ -1599,21 +1590,6 @@ packages: get-func-name: 2.0.2 dev: true - /chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - dev: true - /chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -2325,7 +2301,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4 + debug: 4.3.6 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.0 @@ -2464,7 +2440,7 @@ packages: engines: {node: '>= 10.17.0'} hasBin: true dependencies: - debug: 4.3.4 + debug: 4.3.6 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -2515,13 +2491,6 @@ packages: flat-cache: 3.0.4 dev: true - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: true - /fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -2724,6 +2693,7 @@ packages: /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -2824,7 +2794,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.6 transitivePeerDependencies: - supports-color dev: true @@ -2868,6 +2838,7 @@ packages: /inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. dependencies: once: 1.4.0 wrappy: 1.0.2 @@ -3168,6 +3139,7 @@ packages: /loupe@2.3.6: resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} + deprecated: Please upgrade to 2.3.7 which fixes GHSA-4q6p-r6v2-jvc5 dependencies: get-func-name: 2.0.2 dev: true @@ -3187,7 +3159,7 @@ packages: resolution: {integrity: sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==} engines: {node: '>=12'} dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 dev: true /merge-stream@2.0.0: @@ -3203,7 +3175,7 @@ packages: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} dependencies: - braces: 3.0.2 + braces: 3.0.3 picomatch: 2.3.1 dev: true @@ -3291,15 +3263,6 @@ packages: /mlly@0.5.17: resolution: {integrity: sha512-Rn+ai4G+CQXptDFSRNnChEgNr+xAEauYhwRvpPl/UHStTlgkIftplgJRsA2OXPuoUn86K4XAjB26+x5CEvVb6A==} - dependencies: - acorn: 8.10.0 - pathe: 1.1.1 - pkg-types: 1.0.2 - ufo: 1.1.1 - dev: true - - /mlly@1.2.0: - resolution: {integrity: sha512-+c7A3CV0KGdKcylsI6khWyts/CYrGTrRVo4R/I7u/cUsy0Conxa6LUhiEzVKIw14lc2L5aiO4+SeVe4TeGRKww==} dependencies: acorn: 8.10.0 pathe: 1.1.1 @@ -3342,7 +3305,7 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true dependencies: - picocolors: 1.0.0 + picocolors: 1.0.1 dev: true /nanoid@3.3.6: @@ -3354,7 +3317,7 @@ packages: /nanospinner@1.1.0: resolution: {integrity: sha512-yFvNYMig4AthKYfHFl1sLj7B2nkHL4lzdig4osvl9/LdGbXwrdFRoqBS98gsEsOakr0yH+r5NZ/1Y9gdVB8trA==} dependencies: - picocolors: 1.0.0 + picocolors: 1.0.1 dev: true /natural-compare-lite@1.4.0: @@ -3452,6 +3415,7 @@ packages: /ohmyfetch@0.4.21: resolution: {integrity: sha512-VG7f/JRvqvBOYvL0tHyEIEG7XHWm7OqIfAs6/HqwWwDfjiJ1g0huIpe5sFEmyb+7hpFa1EGNH2aERWR72tlClw==} + deprecated: Package renamed to https://github.com/unjs/ofetch dependencies: destr: 1.2.2 node-fetch-native: 0.1.8 @@ -3583,10 +3547,6 @@ packages: resolution: {integrity: sha512-6Y6s0vT112P3jD8dGfuS6r+lpa0qqNrLyHPOwvXMnyNTQaYiwgau2DP3aNDsR13xqtGj7rrPo+jFUATpU6/s+g==} dev: true - /pathe@1.1.0: - resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} - dev: true - /pathe@1.1.1: resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} dev: true @@ -3603,10 +3563,6 @@ packages: resolution: {integrity: sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ==} dev: true - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true - /picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} dev: true @@ -3636,14 +3592,6 @@ packages: pathe: 0.3.9 dev: true - /pkg-types@1.0.2: - resolution: {integrity: sha512-hM58GKXOcj8WTqUXnsQyJYXdeAPbythQgEF3nTcEo+nkD49chjQ9IKm/QJy9xf6JakXptz86h7ecP2024rrLaQ==} - dependencies: - jsonc-parser: 3.2.0 - mlly: 1.4.2 - pathe: 1.1.1 - dev: true - /pkg-types@1.0.3: resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} dependencies: @@ -3673,21 +3621,12 @@ packages: lilconfig: 3.1.2 dev: true - /postcss@8.4.23: - resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: true - /postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.6 - picocolors: 1.0.0 + picocolors: 1.0.1 source-map-js: 1.0.2 dev: true @@ -3729,11 +3668,6 @@ packages: once: 1.4.0 dev: true - /punycode@2.3.0: - resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} - engines: {node: '>=6'} - dev: true - /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -3845,6 +3779,7 @@ packages: /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true dependencies: glob: 7.2.3 @@ -3978,13 +3913,13 @@ packages: hasBin: true dependencies: bytes-iec: 3.1.1 - chokidar: 3.5.3 + chokidar: 3.6.0 ci-job-number: 1.2.2 globby: 11.1.0 lilconfig: 2.1.0 mkdirp: 1.0.4 nanospinner: 1.1.0 - picocolors: 1.0.0 + picocolors: 1.0.1 dev: true /slash@3.0.0: @@ -4373,10 +4308,6 @@ packages: resolution: {integrity: sha512-fk6CmUgwKCfX79EzcDQQpSCMxrHstvbLswFChHS0Vump+kFkw7nJBfTZoC1j0bOGoY9I7R3n2DGek5ajbcYnOw==} dev: true - /ufo@1.1.1: - resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==} - dev: true - /ufo@1.3.1: resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==} dev: true @@ -4412,7 +4343,7 @@ packages: /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: - punycode: 2.3.0 + punycode: 2.3.1 dev: true /util-deprecate@1.0.2: @@ -4425,10 +4356,10 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.3.4 + debug: 4.3.6 mlly: 1.4.2 pathe: 1.1.1 - picocolors: 1.0.0 + picocolors: 1.0.1 vite: 4.4.11(@types/node@18.16.2) transitivePeerDependencies: - '@types/node' @@ -4458,7 +4389,7 @@ packages: optional: true dependencies: esbuild: 0.14.54 - postcss: 8.4.23 + postcss: 8.4.31 resolve: 1.22.2 rollup: 2.77.3 optionalDependencies: @@ -4544,11 +4475,11 @@ packages: acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.10 - debug: 4.3.4 + debug: 4.3.6 local-pkg: 0.4.3 magic-string: 0.30.4 pathe: 1.1.1 - picocolors: 1.0.0 + picocolors: 1.0.1 std-env: 3.4.3 strip-literal: 1.3.0 tinybench: 2.8.0 diff --git a/src/task.ts b/src/task.ts index 17627c5..d7ed277 100644 --- a/src/task.ts +++ b/src/task.ts @@ -10,7 +10,7 @@ import Bench from './bench'; import tTable from './constants'; import { createBenchEvent } from './event'; import { AddEventListenerOptionsArgument, RemoveEventListenerOptionsArgument } from './types'; -import { getVariance, isAsyncTask } from './utils'; +import { getVariance, isAsyncTask, quantileSorted } from './utils'; /** * A class that represents each benchmark task in Tinybench. It keeps track of the @@ -152,11 +152,10 @@ export default class Task extends EventTarget { const moe = sem * critical; const rme = (moe / mean) * 100; - // mitata: https://github.com/evanwashere/mitata/blob/3730a784c9d83289b5627ddd961e3248088612aa/src/lib.mjs#L12 - const p75 = samples[Math.ceil(samplesLength * 0.75) - 1]!; - const p99 = samples[Math.ceil(samplesLength * 0.99) - 1]!; - const p995 = samples[Math.ceil(samplesLength * 0.995) - 1]!; - const p999 = samples[Math.ceil(samplesLength * 0.999) - 1]!; + const p75 = quantileSorted(samples, 0.75); + const p99 = quantileSorted(samples, 0.99); + const p995 = quantileSorted(samples, 0.995); + const p999 = quantileSorted(samples, 0.999); if (this.bench.signal?.aborted) { return this; diff --git a/src/utils.ts b/src/utils.ts index c674164..382e41e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,6 +7,32 @@ export const hrtimeNow = () => nanoToMs(Number(process.hrtime.bigint())); export const now = () => performance.now(); +export const quantileSorted = (arr: number[], q: number) => { + if (arr.length === 0) { + throw new Error('arr must not be empty'); + } + if (q < 0 || q > 1) { + throw new Error('q must be between 0 and 1'); + } + if (q === 0) { + return arr[0]; + } + if (q === 1) { + return arr[arr.length - 1]; + } + const base = (arr.length - 1) * q; + const baseIndex = Math.floor(base); + if (arr[baseIndex + 1] != null) { + return ( + // @ts-expect-error: array cannot be empty + (arr[baseIndex]) + // @ts-expect-error: false positive + + (base - baseIndex) * ((arr[baseIndex + 1]) - arr[baseIndex]) + ); + } + return arr[baseIndex]; +}; + function isPromiseLike(maybePromiseLike: any): maybePromiseLike is PromiseLike { return ( maybePromiseLike !== null @@ -16,7 +42,7 @@ function isPromiseLike(maybePromiseLike: any): maybePromiseLike is PromiseLik } /** - * Computes the variance of a sample. + * Computes the variance of a sample with Bessel's correction. */ export const getVariance = (samples: number[], mean: number) => { const result = samples.reduce((sum, n) => sum + ((n - mean) ** 2), 0); diff --git a/tsconfig.json b/tsconfig.json index 3901d0e..5d618aa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ "skipLibCheck": true, "lib": [] }, - "include": ["src", "test", "@types"], + "include": ["src", "test"], "exclude": ["node_modules", "dist"], "removeComments": true, "newLine": "lf" From 26458b0dab6f94ef85daf663c4a597716d978f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Mon, 30 Sep 2024 15:48:28 +0200 Subject: [PATCH 2/7] refactor: align arguments namespace with existing one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/utils.ts | 68 ++++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 382e41e..0bc815d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,32 +7,6 @@ export const hrtimeNow = () => nanoToMs(Number(process.hrtime.bigint())); export const now = () => performance.now(); -export const quantileSorted = (arr: number[], q: number) => { - if (arr.length === 0) { - throw new Error('arr must not be empty'); - } - if (q < 0 || q > 1) { - throw new Error('q must be between 0 and 1'); - } - if (q === 0) { - return arr[0]; - } - if (q === 1) { - return arr[arr.length - 1]; - } - const base = (arr.length - 1) * q; - const baseIndex = Math.floor(base); - if (arr[baseIndex + 1] != null) { - return ( - // @ts-expect-error: array cannot be empty - (arr[baseIndex]) - // @ts-expect-error: false positive - + (base - baseIndex) * ((arr[baseIndex + 1]) - arr[baseIndex]) - ); - } - return arr[baseIndex]; -}; - function isPromiseLike(maybePromiseLike: any): maybePromiseLike is PromiseLike { return ( maybePromiseLike !== null @@ -41,14 +15,6 @@ function isPromiseLike(maybePromiseLike: any): maybePromiseLike is PromiseLik ); } -/** - * Computes the variance of a sample with Bessel's correction. - */ -export const getVariance = (samples: number[], mean: number) => { - const result = samples.reduce((sum, n) => sum + ((n - mean) ** 2), 0); - return result / (samples.length - 1) || 0; -}; - // eslint-disable-next-line @typescript-eslint/no-empty-function const AsyncFunctionConstructor = (async () => {}).constructor; @@ -90,3 +56,37 @@ export const isAsyncTask = async (task: Task) => { return false; } }; + +/** + * Computes the variance of a sample with Bessel's correction. + */ +export const getVariance = (samples: number[], mean: number) => { + const result = samples.reduce((sum, n) => sum + ((n - mean) ** 2), 0); + return result / (samples.length - 1) || 0; +}; + +export const quantileSorted = (samples: number[], q: number) => { + if (samples.length === 0) { + throw new Error('samples must not be empty'); + } + if (q < 0 || q > 1) { + throw new Error('q must be between 0 and 1'); + } + if (q === 0) { + return samples[0]; + } + if (q === 1) { + return samples[samples.length - 1]; + } + const base = (samples.length - 1) * q; + const baseIndex = Math.floor(base); + if (samples[baseIndex + 1] != null) { + return ( + // @ts-expect-error: array cannot be empty + (samples[baseIndex]) + // @ts-expect-error: false positive + + (base - baseIndex) * ((samples[baseIndex + 1]) - samples[baseIndex]) + ); + } + return samples[baseIndex]; +}; From 3a3d1781b5b58f665361a4062c3a5b792ef242a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Mon, 30 Sep 2024 16:46:15 +0200 Subject: [PATCH 3/7] refactor: remove uneeded `@ts-expect-error` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/utils.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 0bc815d..849f67a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -82,10 +82,8 @@ export const quantileSorted = (samples: number[], q: number) => { const baseIndex = Math.floor(base); if (samples[baseIndex + 1] != null) { return ( - // @ts-expect-error: array cannot be empty - (samples[baseIndex]) - // @ts-expect-error: false positive - + (base - baseIndex) * ((samples[baseIndex + 1]) - samples[baseIndex]) + (samples[baseIndex]!) + + (base - baseIndex) * ((samples[baseIndex + 1]!) - samples[baseIndex]!) ); } return samples[baseIndex]; From 7fa19a99f9fb61ef3b4e786bab615760d3b53139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Mon, 30 Sep 2024 17:37:22 +0200 Subject: [PATCH 4/7] chore: align pnpm version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- package.json | 4 ++-- pnpm-lock.yaml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 553e946..8a14260 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,10 @@ "name": "tinybench", "version": "2.9.0", "type": "module", - "packageManager": "pnpm@8.4.0", + "packageManager": "pnpm@8.15.8", "volta": { "node": "20.17.0", - "pnpm": "8.4.0" + "pnpm": "8.15.8" }, "scripts": { "dev": "tsup --watch", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2048ff..ee6a3a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + importers: .: @@ -4623,7 +4627,3 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} dev: true - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false From caba705a4e26c3ea81f5bdbb16f6db4a866013de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 13 Oct 2024 14:20:18 +0200 Subject: [PATCH 5/7] docs: improve code comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/utils.ts | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 849f67a..3a0db7e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -57,14 +57,39 @@ export const isAsyncTask = async (task: Task) => { } }; +/** + * Computes the average of a sample. + * + * @param samples the sample + * @returns the average of the sample + */ +export const average = (samples: number[]) => { + if (samples.length === 0) { + throw new Error('samples must not be empty'); + } + + return samples.reduce((a, b) => a + b, 0) / samples.length; +}; + /** * Computes the variance of a sample with Bessel's correction. + * + * @param samples the sample + * @param avg the average of the sample + * @returns the variance of the sample */ -export const getVariance = (samples: number[], mean: number) => { - const result = samples.reduce((sum, n) => sum + ((n - mean) ** 2), 0); +export const getVariance = (samples: number[], avg = average(samples)) => { + const result = samples.reduce((sum, n) => sum + ((n - avg) ** 2), 0); return result / (samples.length - 1) || 0; }; +/** + * Computes the q-quantile of a sample. + * + * @param samples the sample + * @param q the quantile to compute + * @returns the q-quantile of the sample + */ export const quantileSorted = (samples: number[], q: number) => { if (samples.length === 0) { throw new Error('samples must not be empty'); From 4e51cedb6c0ecce8dea43c03141d0cc7d9b50f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 13 Oct 2024 14:27:57 +0200 Subject: [PATCH 6/7] docs: improve code comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 3a0db7e..8d6442c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -84,9 +84,9 @@ export const getVariance = (samples: number[], avg = average(samples)) => { }; /** - * Computes the q-quantile of a sample. + * Computes the q-quantile of a sorted sample. * - * @param samples the sample + * @param samples the sorted sample * @param q the quantile to compute * @returns the q-quantile of the sample */ From 66fe7a93f95f477779011a3893bde8b09af96305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 13 Oct 2024 14:32:56 +0200 Subject: [PATCH 7/7] fix: add average computation fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 8d6442c..ce5da53 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -68,7 +68,7 @@ export const average = (samples: number[]) => { throw new Error('samples must not be empty'); } - return samples.reduce((a, b) => a + b, 0) / samples.length; + return samples.reduce((a, b) => a + b, 0) / samples.length || 0; }; /**