Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Immer perf appears to have gotten worse over time, based on updated benchmarks #1152

Open
markerikson opened this issue Jan 2, 2025 · 1 comment
Labels

Comments

@markerikson
Copy link
Contributor

markerikson commented Jan 2, 2025

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:

remove: vanilla (freeze: false)         11.22 µs/iter 
remove: immer5 (freeze: false)          14.23 ms/iter 
remove: immer6 (freeze: false)          13.90 ms/iter 
remove: immer7 (freeze: false)          16.65 ms/iter 
remove: immer8 (freeze: false)          27.72 ms/iter 
remove: immer9 (freeze: false)          66.60 ms/iter 
remove: immer10 (freeze: false)         68.61 ms/iter 
remove: immer10Each (freeze: false)     70.92 ms/iter 
remove: mutative (freeze: false)        36.86 ms/iter 
remove: mutativeCompat (freeze: false)  36.44 ms/iter 
remove: vanilla (freeze: true)          10.13 µs/iter 
remove: immer5 (freeze: true)           11.74 ms/iter 
remove: immer6 (freeze: true)           11.97 ms/iter 
remove: immer7 (freeze: true)           12.14 ms/iter 
remove: immer8 (freeze: true)           19.93 ms/iter 
remove: immer9 (freeze: true)           35.90 ms/iter 
remove: immer10 (freeze: true)          37.86 ms/iter 
remove: immer10Each (freeze: true)      34.54 ms/iter 
remove: mutative (freeze: true)         30.32 ms/iter 
remove: mutativeCompat (freeze: true)   39.66 ms/iter 

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:

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)
@mweststrate
Copy link
Collaborator

mweststrate commented Jan 3, 2025

Hey @markerikson,

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:

  1. 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.
  2. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants