diff --git a/.changeset/famous-islands-invite.md b/.changeset/famous-islands-invite.md
new file mode 100644
index 0000000..0033bad
--- /dev/null
+++ b/.changeset/famous-islands-invite.md
@@ -0,0 +1,5 @@
+---
+"@marco.spiess/enumset": patch
+---
+
+Add benchmark
diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml
index 830e28c..4ca570c 100644
--- a/.github/workflows/node.js.yml
+++ b/.github/workflows/node.js.yml
@@ -30,3 +30,5 @@ jobs:
run: npm test -- --coverage
- if: ${{ matrix.node-version == '26.x' }}
uses: coverallsapp/github-action@v2
+ - if: ${{ matrix.node-version == '26.x' }}
+ run: npm run benchmark
diff --git a/README.md b/README.md
index 41a9501..a6a59ac 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ All state is packed into a single 32-bit integer bitfield.
## Installation
-```
+```shell
npm install @marco.spiess/enumset
```
@@ -43,3 +43,80 @@ expect(enumSet.has(Directions.Left)).toBe(false);
- Iteration order breaks the [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set)
contract. The methods `forEach`, `keys`, `values` and `[Symbol.iterator]` iterate in the ascending order of the enum
values and *not* in insertion order.
+
+## Benchmark
+
+Compared to the native `Set` `EnumSets` perform better for `add` and `delete`.
+For `has` and iteration `EnumSets` are slightly slower.
+For all set composition methods `EnumSets` vastly outperform their native counterparts.
+
+
+Result of a benchmark run in CI
+
+```
+ ✓ bench src/main.benchmark.ts (14 tests) 209391ms
+ ✓ has (2)
+ ✓ empty set 19811ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ native set 21,206,116.26 0.0000 0.1372 0.0000 0.0001 0.0001 0.0001 0.0001 ±0.09% 20636388 fastest
+ enum set 13,198,874.44 0.0001 0.0909 0.0001 0.0001 0.0001 0.0001 0.0001 ±0.08% 12924792
+ ✓ full set 20078ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ native set 21,291,907.56 0.0000 0.0978 0.0000 0.0001 0.0001 0.0001 0.0001 ±0.10% 20736684 fastest
+ enum set 13,237,134.16 0.0001 0.0427 0.0001 0.0001 0.0001 0.0001 0.0001 ±0.07% 13005323
+ ✓ add 11337ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ enum set 14,437,209.78 0.0001 0.0320 0.0001 0.0001 0.0001 0.0001 0.0001 ±0.07% 14219315 fastest
+ native set 4,048,108.96 0.0002 0.3157 0.0003 0.0003 0.0003 0.0003 0.0007 ±0.25% 3965984
+ ✓ delete 11266ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ enum set 14,224,467.32 0.0001 0.0729 0.0001 0.0001 0.0001 0.0001 0.0001 ±0.08% 14015339 fastest
+ native set 4,287,069.25 0.0002 0.3568 0.0002 0.0002 0.0003 0.0004 0.0007 ±0.30% 4186946
+ ✓ keys 22782ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ native set 22,845,624.54 0.0000 0.0415 0.0000 0.0001 0.0001 0.0001 0.0001 ±0.07% 22276472 fastest
+ enum set 22,849,172.68 0.0000 0.0951 0.0000 0.0001 0.0001 0.0001 0.0001 ±0.08% 22254698
+ ✓ values 17108ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ native set 19,279,170.57 0.0000 0.0413 0.0001 0.0001 0.0001 0.0001 0.0001 ±0.08% 18898286 fastest
+ enum set 12,656,450.07 0.0001 18.8421 0.0001 0.0001 0.0001 0.0001 0.0005 ±4.09% 11694214
+ ✓ Symbol(Symbol.iterator) 19436ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ native set 19,442,343.10 0.0000 0.0798 0.0001 0.0001 0.0001 0.0001 0.0001 ±0.08% 19006739 fastest
+ enum set 17,544,892.40 0.0000 66.8953 0.0001 0.0001 0.0001 0.0001 0.0004 ±13.17% 15254944
+ ✓ union 12892ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ enum set 20,008,001.47 0.0000 0.1361 0.0001 0.0001 0.0001 0.0001 0.0001 ±0.08% 19682920 fastest
+ native set 2,583,085.76 0.0003 0.3434 0.0004 0.0004 0.0006 0.0007 0.0011 ±0.48% 2473422
+ ✓ intersection 10870ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ enum set 14,711,000.12 0.0001 0.0299 0.0001 0.0001 0.0001 0.0001 0.0001 ±0.08% 14419920 fastest
+ native set 2,186,253.34 0.0004 0.4180 0.0005 0.0005 0.0006 0.0008 0.0011 ±0.37% 2127560
+ ✓ difference 11318ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ enum set 13,911,659.27 0.0001 0.0480 0.0001 0.0001 0.0001 0.0001 0.0001 ±0.08% 13645951 fastest
+ native set 3,541,035.40 0.0002 0.0290 0.0003 0.0003 0.0004 0.0005 0.0009 ±0.08% 3463004
+ ✓ symmetricDifference 11600ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ enum set 13,746,639.49 0.0001 0.0841 0.0001 0.0001 0.0001 0.0001 0.0001 ±0.11% 13438668 fastest
+ native set 5,153,365.76 0.0002 0.3619 0.0002 0.0002 0.0002 0.0003 0.0006 ±0.40% 4995671
+ ✓ isSubsetOf 15365ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ enum set 21,584,121.56 0.0000 0.1773 0.0000 0.0001 0.0001 0.0001 0.0001 ±0.12% 20949499 fastest
+ native set 5,478,922.03 0.0002 0.0427 0.0002 0.0002 0.0002 0.0002 0.0004 ±0.07% 5405264
+ ✓ isSupersetOf 12293ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ enum set 15,724,180.49 0.0000 0.0490 0.0001 0.0001 0.0001 0.0001 0.0001 ±0.07% 15426052 fastest
+ native set 4,863,504.30 0.0002 0.0346 0.0002 0.0002 0.0004 0.0004 0.0005 ±0.08% 4700775
+ ✓ isDisjointFrom 13232ms
+ name hz min max mean p75 p99 p995 p999 rme samples
+ enum set 16,888,742.09 0.0000 0.0478 0.0001 0.0001 0.0001 0.0001 0.0001 ±0.08% 16538521 fastest
+ native set 4,836,225.74 0.0002 0.0411 0.0002 0.0002 0.0002 0.0003 0.0004 ±0.07% 4765987
+```
+
+
+
+To run the benchmark:
+```shell
+npm run benchmark
+```
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index d03c50b..96b783c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,7 @@
"@microsoft/api-extractor": "7.58.12",
"@stylistic/eslint-plugin": "5.10.0",
"@types/node": "26.2.0",
- "@vitest/coverage-v8": "4.1.10",
+ "@vitest/coverage-v8": "5.0.0-rc.2",
"@vitest/eslint-plugin": "1.6.27",
"eslint": "10.8.1",
"globals": "17.9.0",
@@ -25,7 +25,7 @@
"unplugin-dts": "1.0.3",
"vite": "8.2.1",
"vite-plugin-doctest": "3.0.0",
- "vitest": "4.1.10"
+ "vitest": "5.0.0-rc.2"
}
},
"node_modules/@babel/helper-string-parser": {
@@ -1220,6 +1220,7 @@
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -1360,13 +1361,6 @@
"sprintf-js": "~1.0.2"
}
},
- "node_modules/@standard-schema/spec": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
- "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@stylistic/eslint-plugin": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz",
@@ -1484,6 +1478,7 @@
"integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~8.3.0"
}
@@ -1501,6 +1496,7 @@
"integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.67.0",
@@ -1540,6 +1536,7 @@
"integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.67.0",
"@typescript-eslint/types": "8.67.0",
@@ -1739,29 +1736,29 @@
}
},
"node_modules/@vitest/coverage-v8": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz",
- "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==",
+ "version": "5.0.0-rc.2",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-5.0.0-rc.2.tgz",
+ "integrity": "sha512-ZwYwczFui6PCGwxUmT9kUSmquuF1YUB9QMnARqjdVYOegE5+wqEL1NgoEu3jmjyoplHuC6gwktMTp8Bif27f/w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
- "@vitest/utils": "4.1.10",
- "ast-v8-to-istanbul": "^1.0.0",
+ "ast-v8-to-istanbul": "^1.0.5",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.2.0",
- "magicast": "^0.5.2",
- "obug": "^2.1.1",
- "std-env": "^4.0.0-rc.1",
- "tinyrainbow": "^3.1.0"
+ "magicast": "^0.5.4",
+ "obug": "^2.1.4",
+ "std-env": "^4.2.0",
+ "tinyrainbow": "^3.1.1"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@vitest/browser": "4.1.10",
- "vitest": "4.1.10"
+ "@vitest/browser": "5.0.0-rc.2",
+ "vitest": "5.0.0-rc.2"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -1800,34 +1797,17 @@
}
}
},
- "node_modules/@vitest/expect": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz",
- "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@standard-schema/spec": "^1.1.0",
- "@types/chai": "^5.2.2",
- "@vitest/spy": "4.1.10",
- "@vitest/utils": "4.1.10",
- "chai": "^6.2.2",
- "tinyrainbow": "^3.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
"node_modules/@vitest/mocker": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz",
- "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==",
+ "version": "5.0.0-rc.2",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.0-rc.2.tgz",
+ "integrity": "sha512-nbUdBsadb3otfazI0v99+PQuSWiBzz5obMM5Pb1mpLzdPSEG6gVHYiVLuj0PC2dcoqPjd6dgsB5o0t70RXbs2A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "4.1.10",
+ "@jridgewell/trace-mapping": "0.3.31",
+ "@vitest/spy": "5.0.0-rc.2",
"estree-walker": "^3.0.3",
- "magic-string": "^0.30.21"
+ "magic-string": "^1.2.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
@@ -1845,70 +1825,22 @@
}
}
},
- "node_modules/@vitest/pretty-format": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz",
- "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tinyrainbow": "^3.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/runner": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz",
- "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/utils": "4.1.10",
- "pathe": "^2.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/snapshot": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz",
- "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==",
+ "node_modules/@vitest/mocker/node_modules/magic-string": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.0.tgz",
+ "integrity": "sha512-ptco+HFxTLgjafSLim2LojBSwfg5feBjd+SqyiwdGkzC38UPdZy3zgrHMI2CoTf5fJL38tbHMYWVzIH8BxGqJw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.1.10",
- "@vitest/utils": "4.1.10",
- "magic-string": "^0.30.21",
- "pathe": "^2.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
+ "@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/@vitest/spy": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz",
- "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/utils": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz",
- "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==",
+ "version": "5.0.0-rc.2",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.0-rc.2.tgz",
+ "integrity": "sha512-6/p30efajxGxC3KzLae67vRazEKB8Z8s4l0DwbVJLobXyCHDAQPoVMXeS7nKP8aWHOi8Ec9XBUxFvFPF/NRnYA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@vitest/pretty-format": "4.1.10",
- "convert-source-map": "^2.0.0",
- "tinyrainbow": "^3.1.0"
- },
"funding": {
"url": "https://opencollective.com/vitest"
}
@@ -1948,6 +1880,7 @@
"integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2195,13 +2128,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2390,6 +2316,7 @@
"integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"workspaces": [
"packages/*"
],
@@ -2595,9 +2522,9 @@
}
},
"node_modules/expect-type": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
- "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
+ "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -5049,6 +4976,7 @@
"integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@oxc-project/types": "=0.143.0",
"@rolldown/pluginutils": "^1.0.0"
@@ -5301,16 +5229,19 @@
}
},
"node_modules/tinybench": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
- "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.3.tgz",
+ "integrity": "sha512-8k25iNSZHnzkjp3nrpEmx6YkrTNs41PzOHYc28DR5Z2l/CId/Nzw+Ume/41jCeDDmCUMPuK5GkQ/VxJaJ2P6Qg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.0.0"
+ }
},
"node_modules/tinyexec": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
- "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz",
+ "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5335,9 +5266,9 @@
}
},
"node_modules/tinyrainbow": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
- "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz",
+ "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5396,6 +5327,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5612,6 +5544,7 @@
"integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"lightningcss": "^1.33.0",
"picomatch": "^4.0.5",
@@ -5697,38 +5630,32 @@
}
},
"node_modules/vitest": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz",
- "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/expect": "4.1.10",
- "@vitest/mocker": "4.1.10",
- "@vitest/pretty-format": "4.1.10",
- "@vitest/runner": "4.1.10",
- "@vitest/snapshot": "4.1.10",
- "@vitest/spy": "4.1.10",
- "@vitest/utils": "4.1.10",
- "es-module-lexer": "^2.0.0",
- "expect-type": "^1.3.0",
- "magic-string": "^0.30.21",
- "obug": "^2.1.1",
- "pathe": "^2.0.3",
- "picomatch": "^4.0.3",
- "std-env": "^4.0.0-rc.1",
- "tinybench": "^2.9.0",
- "tinyexec": "^1.0.2",
- "tinyglobby": "^0.2.15",
- "tinyrainbow": "^3.1.0",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "version": "5.0.0-rc.2",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.0-rc.2.tgz",
+ "integrity": "sha512-tSdrRylWxqWHYhyHWUdFd3ASHGXxqGmRcPfjRvir+bcFzJ5se9DLzhvzMPS7F4yLWOPsaaILSMPBbWzMzgC1VA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/mocker": "5.0.0-rc.2",
+ "chai": "^6.2.2",
+ "es-module-lexer": "^2.3.1",
+ "expect-type": "^1.4.0",
+ "magic-string": "^1.2.0",
+ "obug": "^2.1.4",
+ "picomatch": "^4.0.5",
+ "std-env": "^4.2.0",
+ "tinybench": "6.1.3",
+ "tinyexec": "1.3.0",
+ "tinyglobby": "^0.2.17",
"why-is-node-running": "^2.3.0"
},
"bin": {
"vitest": "vitest.mjs"
},
"engines": {
- "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ "node": "^22.12.0 || ^24.0.0 || >=26.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
@@ -5736,16 +5663,16 @@
"peerDependencies": {
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
- "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
- "@vitest/browser-playwright": "4.1.10",
- "@vitest/browser-preview": "4.1.10",
- "@vitest/browser-webdriverio": "4.1.10",
- "@vitest/coverage-istanbul": "4.1.10",
- "@vitest/coverage-v8": "4.1.10",
- "@vitest/ui": "4.1.10",
+ "@types/node": "^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "5.0.0-rc.2",
+ "@vitest/browser-preview": "5.0.0-rc.2",
+ "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0",
+ "@vitest/coverage-istanbul": "5.0.0-rc.2",
+ "@vitest/coverage-v8": "5.0.0-rc.2",
+ "@vitest/ui": "5.0.0-rc.2",
"happy-dom": "*",
"jsdom": "*",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ "vite": "^6.4.0 || ^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"@edge-runtime/vm": {
@@ -5786,6 +5713,16 @@
}
}
},
+ "node_modules/vitest/node_modules/magic-string": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.0.tgz",
+ "integrity": "sha512-ptco+HFxTLgjafSLim2LojBSwfg5feBjd+SqyiwdGkzC38UPdZy3zgrHMI2CoTf5fJL38tbHMYWVzIH8BxGqJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
"node_modules/vscode-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
diff --git a/package.json b/package.json
index 11fbd6f..c1c2b3d 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"build": "tsc && vite build",
"lint": "eslint src/",
"test": "vitest --typecheck --run",
+ "benchmark": "vitest bench",
"changeset": "changeset",
"release": "npm run build && changeset tag && npm publish --provenance --access public"
},
@@ -47,7 +48,7 @@
"@microsoft/api-extractor": "7.58.12",
"@stylistic/eslint-plugin": "5.10.0",
"@types/node": "26.2.0",
- "@vitest/coverage-v8": "4.1.10",
+ "@vitest/coverage-v8": "5.0.0-rc.2",
"@vitest/eslint-plugin": "1.6.27",
"eslint": "10.8.1",
"globals": "17.9.0",
@@ -56,6 +57,11 @@
"unplugin-dts": "1.0.3",
"vite": "8.2.1",
"vite-plugin-doctest": "3.0.0",
- "vitest": "4.1.10"
+ "vitest": "5.0.0-rc.2"
+ },
+ "overrides": {
+ "vite-plugin-doctest": {
+ "vitest": "5.0.0-rc.2"
+ }
}
}
diff --git a/src/main.benchmark.ts b/src/main.benchmark.ts
new file mode 100644
index 0000000..4a9f4e1
--- /dev/null
+++ b/src/main.benchmark.ts
@@ -0,0 +1,205 @@
+import { describe, expect, test } from 'vitest';
+import type { IntegerLessThan32 } from './IntegerLessThan32.ts';
+import { EnumSet } from './main.ts';
+
+const allValues = [...Array(32).keys()] as IntegerLessThan32[];
+const lowerHalfValues = [...Array(16).keys()] as IntegerLessThan32[];
+const upperHalfValues = [...Array(16).keys()].map(value => value + 16) as IntegerLessThan32[];
+
+describe('has', () => {
+ test('empty set', async ({ bench }) => {
+ const nativeSet = new Set();
+ const enumSet = new EnumSet();
+
+ await bench.compare(
+ bench('native set', () => {
+ allValues.forEach((value) => {
+ nativeSet.has(value);
+ });
+ }), bench('enum set', () => {
+ allValues.forEach((value) => {
+ enumSet.has(value);
+ });
+ }),
+ );
+ });
+
+ test('full set', async ({ bench }) => {
+ const nativeSet = new Set(allValues);
+ const enumSet = new EnumSet(allValues);
+
+ await bench.compare(
+ bench('native set', () => {
+ allValues.forEach((value) => {
+ nativeSet.has(value);
+ });
+ }), bench('enum set', () => {
+ allValues.forEach((value) => {
+ enumSet.has(value);
+ });
+ }),
+ );
+ });
+});
+
+test('add', async ({ bench }) => {
+ const nativeSet = new Set();
+ const enumSet = new EnumSet();
+
+ const result = await bench.compare(
+ bench('native set', () => {
+ allValues.forEach((value) => {
+ nativeSet.add(value);
+ });
+ }), bench('enum set', () => {
+ allValues.forEach((value) => {
+ enumSet.add(value);
+ });
+ }),
+ );
+
+ expect([...nativeSet.values()]).toStrictEqual(allValues);
+ expect([...enumSet.values()]).toStrictEqual(allValues);
+ expect(result.get('enum set')).toBeFasterThan(result.get('native set'));
+});
+
+test('delete', async ({ bench }) => {
+ const nativeSet = new Set(allValues);
+ const enumSet = new EnumSet(allValues);
+
+ const result = await bench.compare(
+ bench('native set', () => {
+ allValues.forEach((value) => {
+ nativeSet.delete(value);
+ });
+ }), bench('enum set', () => {
+ allValues.forEach((value) => {
+ enumSet.delete(value);
+ });
+ }),
+ );
+
+ expect(nativeSet.size).toBe(0);
+ expect(enumSet.size).toBe(0);
+ expect(result.get('enum set')).toBeFasterThan(result.get('native set'));
+});
+
+test.for([
+ { method: 'keys' as const },
+ { method: 'values' as const },
+ // workaround for https://github.com/microsoft/TypeScript/issues/54100
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+ { method: Symbol.iterator } as { method: typeof Symbol.iterator },
+])('$method', async ({ method }, { bench }) => {
+ const nativeSet = new Set(allValues);
+ const enumSet = new EnumSet(allValues);
+
+ await bench.compare(
+ bench('native set', () => {
+ nativeSet[method]();
+ }), bench('enum set', () => {
+ enumSet[method]();
+ }),
+ );
+});
+
+test.for<{
+ operation: 'union' | 'intersection' | 'difference' | 'symmetricDifference';
+ leftValues: IntegerLessThan32[];
+ rightValues: IntegerLessThan32[];
+ expectedValues: IntegerLessThan32[];
+}>([
+ {
+ operation: 'union',
+ leftValues: lowerHalfValues,
+ rightValues: upperHalfValues,
+ expectedValues: allValues,
+ },
+ {
+ operation: 'intersection',
+ leftValues: lowerHalfValues,
+ rightValues: allValues,
+ expectedValues: lowerHalfValues,
+ },
+ {
+ operation: 'difference',
+ leftValues: allValues,
+ rightValues: upperHalfValues,
+ expectedValues: lowerHalfValues,
+ },
+ {
+ operation: 'symmetricDifference',
+ leftValues: [0, 1, 2, 3],
+ rightValues: [2, 3, 4, 5],
+ expectedValues: [0, 1, 4, 5],
+ },
+])('$operation', async ({ operation, leftValues, rightValues, expectedValues }, { bench }) => {
+ const nativeSets = [new Set(leftValues), new Set(rightValues)];
+ const enumSets = [new EnumSet(leftValues), new EnumSet(rightValues)];
+ type CompositionMethod = typeof nativeSets[number][typeof operation];
+
+ let nativeResult: Set | undefined;
+ let enumSetResult: Set | undefined;
+
+ const result = await bench.compare(
+ bench('native set', () => {
+ const [left, right] = nativeSets;
+
+ nativeResult = left[operation](right);
+ }),
+ bench('enum set', () => {
+ const [left, right] = enumSets;
+
+ enumSetResult = (left[operation] as CompositionMethod)(right) as EnumSet;
+ }),
+ );
+
+ expect([...nativeResult!.values()]).toEqual(expectedValues);
+ expect([...enumSetResult!.values()]).toEqual(expectedValues);
+ expect(result.get('enum set')).toBeFasterThan(result.get('native set'));
+});
+
+test.for<{
+ operation: 'isDisjointFrom' | 'isSubsetOf' | 'isSupersetOf';
+ leftValues: IntegerLessThan32[];
+ rightValues: IntegerLessThan32[];
+}>([
+ {
+ operation: 'isSubsetOf',
+ leftValues: lowerHalfValues,
+ rightValues: allValues,
+ },
+ {
+ operation: 'isSupersetOf',
+ leftValues: allValues,
+ rightValues: lowerHalfValues,
+ },
+ {
+ operation: 'isDisjointFrom',
+ leftValues: lowerHalfValues,
+ rightValues: upperHalfValues,
+ },
+])('$operation', async ({ operation, leftValues, rightValues }, { bench }) => {
+ const nativeSets = [new Set(leftValues), new Set(rightValues)];
+ const enumSets = [new EnumSet(leftValues), new EnumSet(rightValues)];
+
+ let nativeResult = false;
+ let enumSetResult = false;
+
+ const result = await bench.compare(
+ bench('native set', () => {
+ const [left, right] = nativeSets;
+
+ nativeResult = left[operation](right);
+ }),
+ bench('enum set', () => {
+ const [left, right] = enumSets;
+
+ enumSetResult = left[operation](right);
+ }),
+ );
+
+ expect(nativeResult).toBe(true);
+ expect(enumSetResult).toBe(true);
+ expect(result.get('enum set')).toBeFasterThan(result.get('native set'));
+});