Skip to content

Commit

Permalink
test(benchmark): add benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
unadlib committed Dec 30, 2024
1 parent 60803f1 commit 6000dda
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 3 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ With the Mutative middleware, you can simplify the handling of immutable data in

`zustand-mutative` is 2-6x faster than zustand with spread operation, more than 10x faster than `zustand/middleware/immer`. [Read more about the performance comparison in Mutative](https://mutative.js.org/docs/getting-started/performance).

## Benchmarks

Measure(ops/sec) to update 50K arrays and 1K objects, bigger is better([view source](https://github.com/unadlib/mutative/blob/main/test/performance/benchmark.ts)). [Mutative v1.1.0 vs Immer v10.1.1]

![Benchmark](benchmark.jpg)

```
Zustand with Mutative - Update big array and object x 5,169 ops/sec ±2.09% (85 runs sampled)
Zustand with Immer - Update big array and object x 251 ops/sec ±0.40% (92 runs sampled)
The fastest method is Zustand with Mutative - Update big array and object
```

## Installation

In order to use the Mutative middleware in Zustand, you will need to install Mutative and Zustand as a direct dependency.
Expand Down Expand Up @@ -49,7 +62,6 @@ export const useCountStore = create<State & Actions>()(
);
```


### Mutative Options

- [Strict mode](https://mutative.js.org/docs/advanced-guides/strict-mode)
Expand Down
Binary file added benchmark.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"clean": "rimraf dist",
"build": "yarn clean && tsc --skipLibCheck && yarn build:prod",
"build:prod": "NODE_ENV=production rollup --config --bundleConfigAsCjs",
"commit": "yarn git-cz"
"commit": "yarn git-cz",
"benchmark": "NODE_ENV=production tsx scripts/benchmark.ts"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -54,17 +55,20 @@
"@types/react": "^19.0.1",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"benchmark": "^2.1.4",
"commitizen": "^4.3.0",
"coveralls": "^3.1.1",
"eslint": "^8.36.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-prettier": "^4.2.1",
"immer": "^10.1.1",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"mutative": "^1.1.0",
"prettier": "^2.8.6",
"quickchart-js": "^3.1.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"rimraf": "^4.4.0",
Expand Down
180 changes: 180 additions & 0 deletions scripts/benchmark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import fs from 'fs';
import https from 'https';
import { Suite } from 'benchmark';
import QuickChart from 'quickchart-js';
import { create as createWithZustand } from 'zustand';
import { immer } from 'zustand/middleware/immer';
import { mutative } from '../src';

const labels: string[] = [];
const result = [
{
label: 'Zustand with Mutative',
backgroundColor: 'rgba(255, 0, 217, 0.5)',
data: [],
},
{
label: 'Zustand with Immer',
backgroundColor: 'rgba(255, 0, 0, 0.5)',
data: [],
},
];

interface Data {
arr: Record<string, number>[];
map: Record<string, Record<string, number>>;
}

type Store = Data & {
update: () => void;
};

const getData = () => {
const baseState: Data = {
arr: [],
map: {},
};

const createTestObject = () =>
Array(10 * 5)
.fill(1)
.reduce((i, _, k) => Object.assign(i, { [k]: k }), {});

baseState.arr = Array(10 ** 4 * 5)
.fill('')
.map(() => createTestObject());

Array(10 ** 3)
.fill(1)
.forEach((_, i) => {
baseState.map[i] = { i };
});
return baseState;
// return deepFreeze(baseState);
};

let baseState: any;
let i: any;
let store: any;

const suite = new Suite();

suite
.add(
'Zustand with Mutative - Update big array and object',
() => {
store.getState().update();
},
{
onStart: () => {
i = Math.random();
baseState = getData();
store = createWithZustand(
mutative<Store>((set) => ({
arr: baseState.arr,
map: baseState.map,
update: () => {
set((state) => {
state.arr.push(i);
state.map[i] = { i };
});
},
}))
);
},
}
)
.add(
'Zustand with Immer - Update big array and object',
() => {
store.getState().update();
},
{
onStart: () => {
i = Math.random();
baseState = getData();
store = createWithZustand(
immer<Store>((set) => ({
arr: baseState.arr,
map: baseState.map,
update: () => {
set((state) => {
state.arr.push(i);
state.map[i] = { i };
});
},
}))
);
},
}
)
.on('cycle', (event: any) => {
console.log(String(event.target));
const [name, field = 'Update'] = event.target.name.split(' - ');
if (!labels.includes(field)) labels.push(field);
const item = result.find(({ label }) => label === name);
// @ts-ignore
item.data[labels.indexOf(field)] = Math.round(event.target.hz);
})
.on('complete', function (this: any) {
console.log(`The fastest method is ${this.filter('fastest').map('name')}`);
})
.run({ async: false });

try {
const config = {
type: 'horizontalBar',
data: {
labels,
datasets: result,
},
options: {
title: {
display: true,
text: 'Zustand with Mutative vs Zustand vs Zustand with Immer - Performance',
},
legend: {
position: 'bottom',
},
elements: {
rectangle: {
borderWidth: 1,
},
},
scales: {
xAxes: [
{
display: true,
scaleLabel: {
display: true,
fontSize: 10,
labelString:
'Measure(ops/sec) to update 50K arrays and 1K objects, bigger is better.',
},
},
],
},
plugins: {
datalabels: {
anchor: 'center',
align: 'center',
font: {
size: 8,
},
},
},
},
};
const chart = new QuickChart();
chart.setConfig(config);
const file = fs.createWriteStream('benchmark.jpg');
https.get(chart.getUrl(), (response) => {
response.pipe(file);
file.on('finish', () => {
file.close();
console.log('update benchmark');
});
});
} catch (err) {
console.error(err);
}
65 changes: 64 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1612,6 +1612,14 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"

benchmark@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629"
integrity sha512-l9MlfN4M1K/H2fbhfMy3B7vJd6AGKJVQn2h6Sg/Yx+KckoUA7ewS5Vv6TjSq18ooE1kS9hhAlQRH3AkXIh/aOQ==
dependencies:
lodash "^4.17.4"
platform "^1.3.3"

bl@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
Expand Down Expand Up @@ -1956,6 +1964,13 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==

cross-fetch@^3.1.5:
version "3.2.0"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.2.0.tgz#34e9192f53bc757d6614304d9e5e6fb4edb782e3"
integrity sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==
dependencies:
node-fetch "^2.7.0"

cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
Expand Down Expand Up @@ -3144,6 +3159,11 @@ ignore@^5.3.1:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==

immer@^10.1.1:
version "10.1.1"
resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.1.tgz#206f344ea372d8ea176891545ee53ccc062db7bc"
integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==

import-fresh@^3.2.1, import-fresh@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
Expand Down Expand Up @@ -3489,6 +3509,11 @@ jake@^10.8.5:
filelist "^1.0.4"
minimatch "^3.1.2"

javascript-stringify@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-2.1.0.tgz#27c76539be14d8bd128219a2d731b09337904e79"
integrity sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==

jest-changed-files@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a"
Expand Down Expand Up @@ -4089,7 +4114,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==

lodash@4.17.21, lodash@^4.17.21:
lodash@4.17.21, lodash@^4.17.21, lodash@^4.17.4:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
Expand Down Expand Up @@ -4337,6 +4362,13 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==

node-fetch@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"

node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
Expand Down Expand Up @@ -4598,6 +4630,11 @@ pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"

platform@^1.3.3:
version "1.3.6"
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7"
integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==

possible-typed-array-names@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f"
Expand Down Expand Up @@ -4686,6 +4723,14 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==

quickchart-js@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/quickchart-js/-/quickchart-js-3.1.3.tgz#7352df8e35b66ce4f50d6ea51252cee2e5213962"
integrity sha512-QzPUXBA/UntYBbOMITtMz7B426fes1XFmmjmjA070jXeMWhyhDojJf2aSZPsekj35ywfJhWjY6TKf3S0/XxyAg==
dependencies:
cross-fetch "^3.1.5"
javascript-stringify "^2.1.0"

randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
Expand Down Expand Up @@ -5329,6 +5374,11 @@ tr46@^3.0.0:
dependencies:
punycode "^2.1.1"

tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==

trim-lines@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"
Expand Down Expand Up @@ -5663,6 +5713,11 @@ wcwidth@^1.0.1:
dependencies:
defaults "^1.0.3"

webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==

webidl-conversions@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
Expand All @@ -5688,6 +5743,14 @@ whatwg-url@^11.0.0:
tr46 "^3.0.0"
webidl-conversions "^7.0.0"

whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"

which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
Expand Down

0 comments on commit 6000dda

Please sign in to comment.