You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I've put together some updated benchmarks for various versions of Immer and other immutable update libs vs a hand-written reducer, especially since the current docs at https://immerjs.github.io/immer/performance show benchmarks that were last run against Node 10 and much older versions of Immer.
Overall, it does appear that Immer is significantly slower than both hand-written reducers and mutative. It looks like the majority of that time is due to freezing, but it also appears that Immer's perf has gotten worse over time.
I know that Immer has a lot of logic for correctness. I'm not sure how much performance optimization can be wrung out of the current approach, but the results here do seem concerning, and I figured this was worth sharing for discussion.
Overview
It looks like Immer's perf got significantly worse starting with Immer 8. This is especially true with freezing turned on, but also even with freezing turned off. (This is admittedly a bit surprising given that Immer 8's changes were basically just turning on freezing by default, no other major logic changes.)
As an example, note this set of results for one benchmark case. The vanilla reducer is 11 microseconds. Immer is in the milliseconds range, and for both freezing and non-freezing Immer gets worse over time:
In that issue, @gentlee set up both a vanilla hand-written reducer, and an RTK createSlice reducer with Immer, and compared them in four scenarios dealing with a nested large array (add, update, remove, concat + truncate).
The issue discussion noted that Immer appears to be significantly slower than hand-written - not just 2-3x, but 100x or more.
I've taken the sample scenarios from that repo, and created another new benchmark repo at https://github.com/markerikson/immer-perf-tests that improves on the benchmarking process in a few ways:
drops all the Redux usage so we're testing just the reducer update times
Tests against multiple versions of Immer (including a locally built copy of the WIP faster-iteration-experiment branch from Faster iteration experiment #1120 )
Runs the same tests for each scenario and library with and without freezing
I did run this on a few different Node versions and got roughly similar results each time. I also tried to run it in a browser, but ran into issues with Vite failing to load a nested dependency for the mitata benchmarking lib, so didn't get that working atm.
Detailed Output
Here's the output of a benchmark run:
clk: ~3.47 GHz
cpu: AMD Ryzen 7 5800H with Radeon Graphics
runtime: node 18.18.2 (x64-win32)
benchmark avg (min … max) p75 p99 (min … top 1%)
----------------------------------------------------- -------------------------------
add: vanilla (freeze: false) 27.24 µs/iter 28.45 µs █
(23.72 µs … 36.50 µs) 31.07 µs █▁███▁██▁▁█▁▁██▁▁▁▁▁█
add: immer5 (freeze: false) 584.93 µs/iter 682.10 µs █▂
(420.10 µs … 1.89 ms) 1.36 ms ██▅▃▂▂▂▃▃▂▂▂▂▁▂▁▁▁▁▁▁
add: immer6 (freeze: false) 497.48 µs/iter 616.50 µs █
(318.80 µs … 1.30 ms) 1.01 ms ▁▃█▃▃▂▂▂▂▂▂▃▂▂▁▁▁▁▁▁▁
add: immer7 (freeze: false) 652.56 µs/iter 638.40 µs █
(477.50 µs … 2.13 ms) 1.34 ms ▁▂█▃▂▂▁▁▁▁▂▁▂▂▁▁▁▁▁▁▁
add: immer8 (freeze: false) 3.63 ms/iter 4.12 ms █
(2.75 ms … 6.20 ms) 5.46 ms ▃██▇███▃▄▂▅▄▅▂▂▁▅▂▁▃▁
add: immer9 (freeze: false) 7.44 ms/iter 8.70 ms █ ▅ ▅
(5.70 ms … 11.57 ms) 10.80 ms ▃█████▆▃▃▃▃▁▆▇▁▇▂▃▁▂▄
add: immer10 (freeze: false) 7.19 ms/iter 8.06 ms ▂▄█▆▃ ▂
(5.56 ms … 11.55 ms) 11.25 ms ██████▄█▄▆▂▄▆▅▇▂▁▁▁▁▂
add: immer10Each (freeze: false) 8.74 ms/iter 10.50 ms █▅ ▃ ▃
(5.78 ms … 14.49 ms) 13.33 ms ▄████▄▃▆▃██▄▆██▃▃▄▄▃▃
add: mutative (freeze: false) 51.38 µs/iter 50.20 µs █
(24.70 µs … 792.30 µs) 422.70 µs ▆█▄▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
add: mutativeCompat (freeze: false) 49.22 µs/iter 48.00 µs █
(20.90 µs … 890.20 µs) 397.50 µs ▆█▅▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
add: vanilla (freeze: true) 36.32 µs/iter 39.74 µs █ ██ █
(30.24 µs … 46.45 µs) 41.64 µs █▁█▁█▁▁▁▁██▁▁▁▁▁▁█▁▁█
add: immer5 (freeze: true) 831.42 µs/iter 932.10 µs ▂ █▇
(423.70 µs … 2.29 ms) 1.72 ms █▇▄▅▅▄███▅▄▂▃▃▂▂▂▂▁▁▁
add: immer6 (freeze: true) 677.55 µs/iter 783.00 µs ▆ █
(355.40 µs … 2.01 ms) 1.52 ms ▆█▃▄▃▃██▄▅▃▃▂▂▂▁▁▁▁▁▁
add: immer7 (freeze: true) 946.48 µs/iter 1.15 ms █▂ ▂
(569.10 µs … 2.08 ms) 1.70 ms ██▆▆▅▆▅▄▆███▅▄▄▃▂▂▁▁▁
add: immer8 (freeze: true) 4.53 ms/iter 5.15 ms ▇▆ ▂ ▂▇█▆▃▇▃▂
(2.99 ms … 6.53 ms) 6.24 ms ███▄▂▅█▇█████████▇▅▄▂
add: immer9 (freeze: true) 10.56 ms/iter 11.58 ms ▆▂▄▄ ▆ █
(7.32 ms … 13.09 ms) 13.05 ms ▃▇▁▅▁▃▅▃████▅█▅█▅▇▅▃▅
add: immer10 (freeze: true) 9.37 ms/iter 10.48 ms ▄█ ▄
(5.99 ms … 12.68 ms) 12.54 ms ▃▇▇▃▇▃▇█▇▅███████▅▃▁▃
add: immer10Each (freeze: true) 7.66 ms/iter 9.19 ms ▄█▃
(5.88 ms … 11.36 ms) 11.12 ms ████▆▂▁▄▂▂▃▆▁▄▆▆▁▅▂▂▃
add: mutative (freeze: true) 43.90 µs/iter 48.69 µs █ █
(35.39 µs … 54.02 µs) 49.92 µs █▁██▁▁▁▁▁█▁▁▁██▁▁▁███
add: mutativeCompat (freeze: true) 718.40 µs/iter 884.40 µs ▆█
(528.20 µs … 2.17 ms) 1.44 ms ██▅▄▂▁▂▄▃▃▅▄▃▁▁▁▁▁▁▁▁
summary
add: vanilla (freeze: false)
1.33x faster than add: vanilla (freeze: true)
1.61x faster than add: mutative (freeze: true)
1.81x faster than add: mutativeCompat (freeze: false)
1.89x faster than add: mutative (freeze: false)
18.27x faster than add: immer6 (freeze: false)
21.48x faster than add: immer5 (freeze: false)
23.96x faster than add: immer7 (freeze: false)
24.88x faster than add: immer6 (freeze: true)
26.38x faster than add: mutativeCompat (freeze: true)
30.53x faster than add: immer5 (freeze: true)
34.75x faster than add: immer7 (freeze: true)
133.19x faster than add: immer8 (freeze: false)
166.19x faster than add: immer8 (freeze: true)
263.92x faster than add: immer10 (freeze: false)
273.09x faster than add: immer9 (freeze: false)
281.16x faster than add: immer10Each (freeze: true)
320.97x faster than add: immer10Each (freeze: false)
343.94x faster than add: immer10 (freeze: true)
387.65x faster than add: immer9 (freeze: true)
----------------------------------------------------- -------------------------------
remove: vanilla (freeze: false) 11.22 µs/iter 11.47 µs ███ █ █ ██ █ █
(10.02 µs … 12.91 µs) 12.64 µs ███▁█▁█▁▁▁██▁▁▁█▁▁▁▁█
remove: immer5 (freeze: false) 14.23 ms/iter 16.36 ms ▃ █ ▃▃▃▃ ▃███ █ █▃ ▃
(9.33 ms … 18.54 ms) 18.53 ms █▆▆█▆████▁████▆█▆██▆█
remove: immer6 (freeze: false) 13.90 ms/iter 16.01 ms █ ▄▄█ ▄ ▄ ▄
(10.00 ms … 19.58 ms) 18.37 ms ███████▁▁██▅▅▅█▅██▅█▅
remove: immer7 (freeze: false) 16.65 ms/iter 18.06 ms ▂█
(14.17 ms … 23.15 ms) 21.78 ms ▃██▇▅▁▇▃▃▃▃▁▇▁▅▅▁▁▁▁▃
remove: immer8 (freeze: false) 27.72 ms/iter 29.06 ms █ ▃ █▃
(23.61 ms … 35.63 ms) 33.67 ms ▆█▆▆▁█▆▆▆▁▁██▁▁▁▁▁▆▁▆
remove: immer9 (freeze: false) 66.60 ms/iter 74.04 ms ▃ █
(54.38 ms … 75.73 ms) 74.20 ms ▆▆▁▁▁▁▁█▁▁▁▆▆▆▁▁▁▆▁▁█
remove: immer10 (freeze: false) 68.61 ms/iter 72.11 ms ▃ █
(58.88 ms … 91.71 ms) 82.31 ms ▆▁▆█▁█▆▁▁▁▁▆▆▁▁▁▁▁▁▁▆
remove: immer10Each (freeze: false) 70.92 ms/iter 75.19 ms █
(58.99 ms … 88.05 ms) 87.04 ms ███▁███▁▁█▁▁█▁▁▁▁█▁▁█
remove: mutative (freeze: false) 36.86 ms/iter 38.51 ms █ █ █ █ █
(29.47 ms … 45.63 ms) 44.27 ms ██▁▁▁▁█▁██▁▁█▁▁▁▁█▁▁█
remove: mutativeCompat (freeze: false) 36.44 ms/iter 39.51 ms █ █
(31.01 ms … 45.29 ms) 43.54 ms █████▁█▁█▁▁▁▁▁█▁█▁█▁█
remove: vanilla (freeze: true) 10.13 µs/iter 10.09 µs █ █
(9.58 µs … 11.54 µs) 10.67 µs █▁▁██▁█▁██▁█▁▁▁▁▁▁▁▁█
remove: immer5 (freeze: true) 11.74 ms/iter 12.33 ms ▄█▄▆
(9.54 ms … 21.58 ms) 16.29 ms ▅█████▃▁▇▃▁▃▃▇▃▃▁▅▁▁▅
remove: immer6 (freeze: true) 11.97 ms/iter 12.58 ms ▂▂█ ▂
(9.31 ms … 18.54 ms) 18.41 ms ███████▅▅▅▁▁▃▁▁▅▁▃▃▃▃
remove: immer7 (freeze: true) 12.14 ms/iter 12.63 ms █
(10.09 ms … 16.64 ms) 15.33 ms ▆▄▆▆██▄██▃▆▁▃▃▁▁▃▃▃▄▆
remove: immer8 (freeze: true) 19.93 ms/iter 21.95 ms █
(16.63 ms … 25.42 ms) 24.85 ms █▆▄█▁▆▄▁▄▆▁▄▄▄▁▁█▆▁▁▄
remove: immer9 (freeze: true) 35.90 ms/iter 36.86 ms █
(31.76 ms … 42.27 ms) 41.24 ms ▅▁▁█▅▁▅▅▁▅▁▅▁▅▁▁▁▅▁▁▅
remove: immer10 (freeze: true) 37.86 ms/iter 41.62 ms ▃ █
(29.90 ms … 45.22 ms) 44.66 ms ▆▁▁▁█▁▆▁▁█▆▁▁▆▁▁▆▁▁▆▆
remove: immer10Each (freeze: true) 34.54 ms/iter 35.29 ms ▃▃ ▃ █
(29.71 ms … 45.91 ms) 42.98 ms ██▆▆▁▁█▁█▁▆▁▆▁▁▁▁▁▁▁▆
remove: mutative (freeze: true) 30.32 ms/iter 31.23 ms █ ██ █
(28.06 ms … 35.88 ms) 33.35 ms ████▁█▁█▁██▁█▁▁█▁█▁▁█
remove: mutativeCompat (freeze: true) 39.66 ms/iter 40.42 ms ██
(35.04 ms … 48.34 ms) 45.11 ms ████▁▁▁██▁▁█▁▁▁▁▁▁█▁█
summary
remove: vanilla (freeze: true)
1.11x faster than remove: vanilla (freeze: false)
1158.99x faster than remove: immer5 (freeze: true)
1182.45x faster than remove: immer6 (freeze: true)
1198.91x faster than remove: immer7 (freeze: true)
1373.2x faster than remove: immer6 (freeze: false)
1405.14x faster than remove: immer5 (freeze: false)
1644.16x faster than remove: immer7 (freeze: false)
1968.06x faster than remove: immer8 (freeze: true)
2738.04x faster than remove: immer8 (freeze: false)
2994.45x faster than remove: mutative (freeze: true)
3411.15x faster than remove: immer10Each (freeze: true)
3545.68x faster than remove: immer9 (freeze: true)
3599.07x faster than remove: mutativeCompat (freeze: false)
3640.7x faster than remove: mutative (freeze: false)
3739.4x faster than remove: immer10 (freeze: true)
3917.02x faster than remove: mutativeCompat (freeze: true)
6577.26x faster than remove: immer9 (freeze: false)
6775.93x faster than remove: immer10 (freeze: false)
7004.63x faster than remove: immer10Each (freeze: false)
----------------------------------------------------- -------------------------------
update: vanilla (freeze: false) 122.54 µs/iter 145.70 µs █
(49.90 µs … 1.51 ms) 274.20 µs ▃▁▁▁█▃▂▂▂▂▁▂▄▂▁▁▁▁▁▁▁
update: immer5 (freeze: false) 822.09 µs/iter 997.60 µs ▇█
(578.60 µs … 2.11 ms) 1.50 ms ██▅▄▄▅▃▅▅▅▅▅▄▃▂▁▁▂▁▁▁
update: immer6 (freeze: false) 735.52 µs/iter 921.40 µs █
(518.20 µs … 1.85 ms) 1.30 ms ▄██▃▃▂▂▂▂▂▄▃▄▃▃▃▁▂▁▁▁
update: immer7 (freeze: false) 650.78 µs/iter 714.10 µs █
(507.20 µs … 1.56 ms) 1.30 ms ██▃▂▂▂▁▁▂▂▁▂▂▃▂▂▁▁▁▁▁
update: immer8 (freeze: false) 4.62 ms/iter 5.16 ms █▆▄
(3.00 ms … 6.06 ms) 5.89 ms ▄█▄▃▃▃▅▄▄▂▆▆▄███▇▆▄▅▂
update: immer9 (freeze: false) 8.97 ms/iter 10.56 ms █▅
(6.00 ms … 11.30 ms) 11.29 ms ▆▆▄▆██▆▃▁▆█▆▄▃█▃███▆▆
update: immer10 (freeze: false) 7.71 ms/iter 9.20 ms ▇█ ▇ ▄
(5.89 ms … 10.66 ms) 10.38 ms ███▆█▇▁▆▃▆▄▆▄▃▆▆▄█▆▄▆
update: immer10Each (freeze: false) 7.25 ms/iter 8.39 ms █▆
(5.70 ms … 10.54 ms) 10.46 ms ██▅▆█▄▃▅▂▃▃▄▃▃▄▃▂▅▂▄▃
update: mutative (freeze: false) 30.31 µs/iter 21.20 µs ▅▃█
(10.80 µs … 2.96 ms) 71.60 µs ▄███▆▄▃▂▂▁▁▁▁▁▁▁▁▁▁▁▁
update: mutativeCompat (freeze: false) 31.13 µs/iter 23.60 µs █ ▆
(11.90 µs … 3.04 ms) 70.20 µs ▅█▅██▅▃▃▂▂▁▁▁▁▁▁▁▁▁▁▁
update: vanilla (freeze: true) 144.97 µs/iter 186.10 µs █
(96.80 µs … 769.10 µs) 296.00 µs █▄▃▂▂▂▂▂▂█▄▂▁▁▁▁▁▁▁▁▁
update: immer5 (freeze: true) 748.82 µs/iter 929.10 µs ██
(535.20 µs … 1.80 ms) 1.53 ms ██▇▄▃▃▂▂▃▄▄▄▃▂▂▁▁▁▂▁▁
update: immer6 (freeze: true) 761.00 µs/iter 964.80 µs ▂█
(506.10 µs … 1.43 ms) 1.27 ms ███▅▄▄▄▃▃▃▄▅▆▆▅▆▂▂▁▁▁
update: immer7 (freeze: true) 1.01 ms/iter 1.19 ms ▅▃ ▄█▆▆
(553.80 µs … 1.85 ms) 1.75 ms ██▃▄▃▃▃▅████▆▃▂▂▂▂▁▂▁
update: immer8 (freeze: true) 4.45 ms/iter 5.35 ms ▇▅ ▂ ▃ ▂ ▃ ██▅
(2.88 ms … 6.31 ms) 5.96 ms ████▇▇▃▂▄▇█▄███████▃▃
update: immer9 (freeze: true) 7.81 ms/iter 9.36 ms █▃▆
(5.79 ms … 11.39 ms) 11.36 ms ▇███▅█▄▅▂▁▄▄▅▂▆▄▅▇▄▂▄
update: immer10 (freeze: true) 7.70 ms/iter 9.21 ms █ ▂
(5.74 ms … 10.51 ms) 10.40 ms ██▇▄▇▄█▅▂▄▆▅▄▄▂▇▄█▅▆▂
update: immer10Each (freeze: true) 8.16 ms/iter 9.58 ms ▃█ ▃ ▅
(5.88 ms … 11.25 ms) 11.14 ms ██▆▄█▄▄█▆▆██▄▁██▄█▃▃▄
update: mutative (freeze: true) 29.61 µs/iter 21.20 µs █ ▆
(11.40 µs … 2.81 ms) 62.50 µs ▄█▅█▆▄▃▂▂▂▁▁▁▁▁▁▁▁▁▁▁
update: mutativeCompat (freeze: true) 854.26 µs/iter 1.00 ms █
(471.50 µs … 3.36 ms) 2.83 ms █▅▃▄█▆▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁
summary
update: mutative (freeze: true)
1.02x faster than update: mutative (freeze: false)
1.05x faster than update: mutativeCompat (freeze: false)
4.14x faster than update: vanilla (freeze: false)
4.9x faster than update: vanilla (freeze: true)
21.98x faster than update: immer7 (freeze: false)
24.84x faster than update: immer6 (freeze: false)
25.29x faster than update: immer5 (freeze: true)
25.7x faster than update: immer6 (freeze: true)
27.77x faster than update: immer5 (freeze: false)
28.85x faster than update: mutativeCompat (freeze: true)
34.27x faster than update: immer7 (freeze: true)
150.44x faster than update: immer8 (freeze: true)
156.03x faster than update: immer8 (freeze: false)
245.01x faster than update: immer10Each (freeze: false)
259.95x faster than update: immer10 (freeze: true)
260.42x faster than update: immer10 (freeze: false)
263.84x faster than update: immer9 (freeze: true)
275.52x faster than update: immer10Each (freeze: true)
302.96x faster than update: immer9 (freeze: false)
----------------------------------------------------- -------------------------------
concat: vanilla (freeze: false) 45.56 µs/iter 51.30 µs █▆ ▄
(27.80 µs … 593.90 µs) 114.70 µs ██▄▃▇██▄▃▂▂▂▁▁▁▁▁▁▁▁▁
concat: immer5 (freeze: false) 1.25 s/iter 1.31 s █
(1.13 s … 1.34 s) 1.33 s █▁█▁▁▁▁▁█▁████▁▁▁▁███
concat: immer6 (freeze: false) 45.49 µs/iter 47.68 µs █
(39.65 µs … 52.05 µs) 51.01 µs ██▁▁█▁▁▁██▁██▁█▁▁▁▁██
concat: immer7 (freeze: false) 51.85 µs/iter 53.53 µs █ █
(43.33 µs … 66.04 µs) 55.67 µs █▁▁▁▁█▁▁███▁▁█▁▁██▁▁█
concat: immer8 (freeze: false) 52.14 µs/iter 52.96 µs █ ▃ ▃
(44.06 µs … 58.98 µs) 56.71 µs ▆▁▁▁▁▆▁▁▁▁▁█▆█▆▁▁▁▁▁█
concat: immer9 (freeze: false) 59.84 µs/iter 67.30 µs █▅ ▃
(35.10 µs … 962.40 µs) 177.70 µs ██▅▅█▇▄▃▂▂▁▁▁▁▁▁▁▁▁▁▁
concat: immer10 (freeze: false) 144.33 µs/iter 148.81 µs █
(133.44 µs … 155.07 µs) 152.00 µs ██▁▁█▁█▁▁▁▁█▁▁█▁██▁██
concat: immer10Each (freeze: false) 161.44 µs/iter 77.60 µs █
(32.50 µs … 9.75 ms) 5.91 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
concat: mutative (freeze: false) 71.01 µs/iter 63.00 µs █
(30.60 µs … 1.93 ms) 646.30 µs ▆█▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
concat: mutativeCompat (freeze: false) 68.67 µs/iter 61.50 µs █
(30.80 µs … 1.95 ms) 577.10 µs ▇█▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
concat: vanilla (freeze: true) 42.88 µs/iter 44.09 µs █ █
(36.20 µs … 49.88 µs) 49.65 µs █▁▁▁█▁███▁█▁██▁▁▁▁▁▁█
concat: immer5 (freeze: true) 1.47 s/iter 1.54 s █ █
(1.31 s … 1.74 s) 1.58 s █▁█▁▁▁██▁▁▁▁▁█▁█▁█▁██
concat: immer6 (freeze: true) 2.01 ms/iter 2.48 ms █▇ ▆
(1.28 ms … 3.07 ms) 2.95 ms ██▃▃▂▅▄▄▂▂▃▃▄▇██▆▄▅▃▂
concat: immer7 (freeze: true) 1.39 ms/iter 1.72 ms █▄ ▄▂
(879.50 µs … 2.88 ms) 2.36 ms ███▄▄▅▄▄▄▃▇██▄▅▂▂▃▂▂▁
concat: immer8 (freeze: true) 4.45 ms/iter 4.99 ms █ ▂
(3.37 ms … 6.85 ms) 6.82 ms ███▇███▆▄▆▂▄▄▆▄▂▄▁▂▁▂
concat: immer9 (freeze: true) 8.73 ms/iter 10.03 ms ▄██▂
(6.70 ms … 12.96 ms) 12.59 ms ████▃▆▇▁▆▆▇▄▄▁▃▃▄▃▇▄▄
concat: immer10 (freeze: true) 9.32 ms/iter 11.03 ms █▃
(6.93 ms … 12.90 ms) 12.82 ms ██▆█▄▄█▆█▃▃█▄▄▄▆▄█▃▃▆
concat: immer10Each (freeze: true) 9.35 ms/iter 10.59 ms ▂ █ ▂
(6.91 ms … 13.60 ms) 13.08 ms ▆██▇▄█▃▄█▄▇▄▇▃▃▇▄▃▃▄▃
concat: mutative (freeze: true) 59.13 µs/iter 57.30 µs █▃
(29.70 µs … 1.89 ms) 490.50 µs ██▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
concat: mutativeCompat (freeze: true) 1.02 ms/iter 1.15 ms █ ▆
(547.30 µs … 2.86 ms) 2.63 ms █▆▄▄███▃▂▃▂▁▁▁▂▁▂▂▁▁▁
summary
concat: vanilla (freeze: true)
1.06x faster than concat: immer6 (freeze: false)
1.06x faster than concat: vanilla (freeze: false)
1.21x faster than concat: immer7 (freeze: false)
1.22x faster than concat: immer8 (freeze: false)
1.38x faster than concat: mutative (freeze: true)
1.4x faster than concat: immer9 (freeze: false)
1.6x faster than concat: mutativeCompat (freeze: false)
1.66x faster than concat: mutative (freeze: false)
3.37x faster than concat: immer10 (freeze: false)
3.76x faster than concat: immer10Each (freeze: false)
23.73x faster than concat: mutativeCompat (freeze: true)
32.36x faster than concat: immer7 (freeze: true)
46.85x faster than concat: immer6 (freeze: true)
103.7x faster than concat: immer8 (freeze: true)
203.59x faster than concat: immer9 (freeze: true)
217.3x faster than concat: immer10 (freeze: true)
218.11x faster than concat: immer10Each (freeze: true)
29069.38x faster than concat: immer5 (freeze: false)
34212.63x faster than concat: immer5 (freeze: true)
The text was updated successfully, but these errors were encountered:
Thanks! These findings are very interesting. I'll try to dig deeper here and see how the regressions were introduced, and thanks for setting up that repo!
Two high level thoughts jump to mind as to causes:
The reflection method we used changed over time, mostly for correctness reasons around edge cases like non-enumerable, getters, or inherited fields. In my benchmarks it didn't change much, but yours might be more accurate (or V8 has changed meaningfully). However, this is for uncommon scenarios (especially icmw Redux), so it might be worth to introduce a "sloppy" mode where things would be faster.
Freezing is expensive, but primarily done to eliminate branch traversals the next time is drafted. Originally immer didn't deeply prune, but now we do to find drafts that would otherwise accidentally stay around in a case like draft.x = [draft.y] (originally Immer didn't traverse into "new" objects coming from the "outside" like the new array here). However, I want to explore the option to "mark committed / final" instead of "revoke" the draft proxies. Because we know all proxies involved in a recipe, that means that we don't need to scan or rewrite the final tree, at the costs leaving proxies around in the final state. That shouldn't affect semantics, but in the debugger you'd see proxies.
I hope to explore both, but apologies upfront that it might take a while as we'll have a move coming up :)
Edit: related thought, I'm wondering if Redux would overall would be faster if you'd deep-freeze the event object before sending it into the immer reducer.
Summary
I've put together some updated benchmarks for various versions of Immer and other immutable update libs vs a hand-written reducer, especially since the current docs at https://immerjs.github.io/immer/performance show benchmarks that were last run against Node 10 and much older versions of Immer.
Overall, it does appear that Immer is significantly slower than both hand-written reducers and
mutative
. It looks like the majority of that time is due to freezing, but it also appears that Immer's perf has gotten worse over time.I know that Immer has a lot of logic for correctness. I'm not sure how much performance optimization can be wrung out of the current approach, but the results here do seem concerning, and I figured this was worth sharing for discussion.
Overview
It looks like Immer's perf got significantly worse starting with Immer 8. This is especially true with freezing turned on, but also even with freezing turned off. (This is admittedly a bit surprising given that Immer 8's changes were basically just turning on freezing by default, no other major logic changes.)
As an example, note this set of results for one benchmark case. The vanilla reducer is 11 microseconds. Immer is in the milliseconds range, and for both freezing and non-freezing Immer gets worse over time:
Background
There was some extensive discussion of Immer perf and freezing behavior over in the Redux Toolkit repo:
In that issue, @gentlee set up both a vanilla hand-written reducer, and an RTK
createSlice
reducer with Immer, and compared them in four scenarios dealing with a nested large array (add, update, remove, concat + truncate).The issue discussion noted that Immer appears to be significantly slower than hand-written - not just 2-3x, but 100x or more.
I've taken the sample scenarios from that repo, and created another new benchmark repo at https://github.com/markerikson/immer-perf-tests that improves on the benchmarking process in a few ways:
faster-iteration-experiment
branch from Faster iteration experiment #1120 )I did run this on a few different Node versions and got roughly similar results each time. I also tried to run it in a browser, but ran into issues with Vite failing to load a nested dependency for the
mitata
benchmarking lib, so didn't get that working atm.Detailed Output
Here's the output of a benchmark run:
The text was updated successfully, but these errors were encountered: