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

Refactor reactivity system to use version counting and doubly-linked list tracking #10397

Merged
merged 43 commits into from
Feb 25, 2024

Conversation

yyx990803
Copy link
Member

@yyx990803 yyx990803 commented Feb 24, 2024

This PR refactors the core reactivity system to use version counting and a doubly-linked list data structure inspired by Preact signals.

It brings some improvements and solves some issues around computed.

Bug fixes
close #10236
close #10069

PRs made stale by this one
close #10290
close #10354
close #10189
close #9480

@vue/reactivity API surface changes

diff

These are changes to the @vue/reactivity package only and do not affect Vue core's public API surface.

  • Removed pauseScheduling and resetScheduling exports (never publicly documented)
  • Removed deferredComputed export (previously deprecated)
  • Added EffectFlags export (for Vue core internal use)

Memory Usage Improvements

Given a test case with 1000 refs + 2000 computeds (1000 chained pairs) + 1000 effects subscribing to the last computed, comparing the total memory used by these class instances:

  • Before (3.4.19): 1426k
  • After (this PR): 631k (-56%)

A computed now also lazily subscribe to deps only when itself gets the first subscriber, and unsubscribes when it loses all subscribers. This means they are more reliably garbage-collected, including in SSR.

Performance Comparison

Reasonable gains across the board, most notably for single ref invoking multiple effects (+118%~185%) and reading multiple invalidated computeds (+176%~244%).

Full benchmarks for 3.4.19
✓ packages/reactivity/__benchmarks__/reactiveObject.bench.ts (3) 2885ms
     name                                   hz     min      max    mean     p75     p99    p995    p999      rme  samples
   · create reactive obj          2,074,599.05  0.0001  39.0324  0.0005  0.0003  0.0007  0.0009  0.0028  ±22.59%  1054310
   · read reactive obj property   4,203,327.88  0.0001   0.5089  0.0002  0.0002  0.0003  0.0005  0.0005   ±0.34%  2101664   fastest
   · write reactive obj property  1,528,997.70  0.0005   1.8500  0.0007  0.0007  0.0007  0.0015  0.0015   ±0.76%   764499   slowest
 ✓ packages/reactivity/__benchmarks__/ref.bench.ts (4) 6646ms
   ✓ ref (4) 6645ms
     name                       hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · create ref       8,938,522.42  0.0000  1.3041  0.0001  0.0001  0.0002  0.0002  0.0003  ±0.83%  4469262
   · write ref        5,079,804.70  0.0001  0.4775  0.0002  0.0002  0.0003  0.0004  0.0004  ±0.44%  2539903
   · read ref        13,343,581.26  0.0000  1.8668  0.0001  0.0001  0.0001  0.0002  0.0002  ±1.16%  6675135   fastest
   · write/read ref   4,699,702.66  0.0001  0.0682  0.0002  0.0002  0.0003  0.0003  0.0003  ±0.07%  2349852   slowest
 ✓ packages/reactivity/__benchmarks__/reactiveArray.bench.ts (15) 9971ms
     name                                                                       hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · reduce *reactive* array, 10 elements                                53,741.86  0.0170  3.6785  0.0186  0.0178  0.0434  0.0490  0.1264  ±1.53%    26871
   · reduce *reactive* array, 10 elements, only change first value      148,803.25  0.0063  0.7073  0.0067  0.0066  0.0072  0.0129  0.0223  ±0.41%    74402
   · reduce *readonly* array, 10 elements                                66,735.80  0.0130  2.9992  0.0150  0.0140  0.0193  0.0332  0.1046  ±2.45%    33368
   · reduce *raw* array, copied, 10 elements                          1,703,066.43  0.0005  0.7682  0.0006  0.0006  0.0007  0.0012  0.0013  ±0.57%   851534
   · reduce *raw* array, manually triggered, 10 elements              1,771,019.72  0.0005  0.3467  0.0006  0.0005  0.0009  0.0013  0.0013  ±0.39%   885510   fastest
   · reduce *reactive* array, 100 elements                                5,737.86  0.1678  0.7471  0.1743  0.1705  0.3690  0.4368  0.4961  ±0.64%     2869
   · reduce *reactive* array, 100 elements, only change first value      20,597.08  0.0456  0.3917  0.0486  0.0477  0.0725  0.1155  0.2448  ±0.45%    10299
   · reduce *readonly* array, 100 elements                               10,136.68  0.0921  0.7042  0.0987  0.0968  0.1187  0.2222  0.5746  ±0.84%     5069
   · reduce *raw* array, copied, 100 elements                         1,277,887.62  0.0007  0.2239  0.0008  0.0008  0.0009  0.0010  0.0013  ±0.39%   638944
   · reduce *raw* array, manually triggered, 100 elements             1,409,665.50  0.0006  0.2487  0.0007  0.0007  0.0008  0.0008  0.0012  ±0.30%   704833
   · reduce *reactive* array, 1000 elements                                 601.27  1.6015  1.9243  1.6632  1.6737  1.8958  1.9155  1.9243  ±0.31%      301   slowest
   · reduce *reactive* array, 1000 elements, only change first value      2,218.56  0.4270  0.7411  0.4507  0.4506  0.5726  0.6125  0.6790  ±0.29%     1110
   · reduce *readonly* array, 1000 elements                               1,078.02  0.8668  1.3260  0.9276  0.9218  1.2773  1.2922  1.3260  ±0.66%      540
   · reduce *raw* array, copied, 1000 elements                          365,484.16  0.0024  0.2307  0.0027  0.0027  0.0030  0.0031  0.0070  ±0.35%   182743
   · reduce *raw* array, manually triggered, 1000 elements              423,492.43  0.0022  0.1956  0.0024  0.0024  0.0025  0.0028  0.0073  ±0.13%   211747
 ✓ packages/reactivity/__benchmarks__/effect.bench.ts (18) 13062ms
   ✓ effect (18) 13060ms
     name                                                       hz      min      max     mean      p75      p99     p995     p999     rme  samples
   · single ref invoke                                2,449,582.35   0.0003   0.9198   0.0004   0.0004   0.0005   0.0009   0.0013  ±0.61%  1224792
   · create an effect that tracks 1 refs              2,692,591.38   0.0002   1.4220   0.0004   0.0003   0.0008   0.0009   0.0010  ±0.90%  1346296   fastest
   · create an effect that tracks 10 refs               464,818.21   0.0019   1.0188   0.0022   0.0020   0.0047   0.0047   0.0066  ±0.68%   232410
   · create an effect that tracks 100 refs               47,150.39   0.0187   1.2912   0.0212   0.0193   0.0483   0.0737   0.2648  ±1.20%    23576
   · create an effect that tracks 1000 refs               4,800.80   0.1869   1.8498   0.2083   0.1915   0.5777   0.6299   1.2765  ±1.67%     2401
   · create and stop an effect that tracks 1 refs     2,461,279.41   0.0003   0.3055   0.0004   0.0004   0.0005   0.0007   0.0010  ±0.55%  1230640
   · create and stop an effect that tracks 10 refs      407,741.18   0.0022   0.1988   0.0025   0.0024   0.0028   0.0029   0.0063  ±0.41%   203871
   · create and stop an effect that tracks 100 refs      43,723.89   0.0213   0.2984   0.0229   0.0225   0.0255   0.0360   0.1549  ±0.45%    21862
   · create and stop an effect that tracks 1000 refs      4,395.97   0.2122   0.5280   0.2275   0.2237   0.3581   0.3819   0.4186  ±0.47%     2199
   · 10 refs invoke                                      87,209.50   0.0106   0.2296   0.0115   0.0115   0.0142   0.0196   0.0304  ±0.25%    43605
   · 100 refs invoke                                      1,131.92   0.8294   1.1110   0.8835   0.8908   1.0568   1.0805   1.1110  ±0.31%      566
   · 1000 refs invoke                                      11.2561  87.9438  89.8169  88.8408  89.1844  89.8169  89.8169  89.8169  ±0.47%       10   slowest
   · 10 refs branch toggle                              794,424.68   0.0006   0.1580   0.0013   0.0017   0.0020   0.0021   0.0055  ±0.34%   397213
   · 100 refs branch toggle                             122,703.79   0.0031   0.2428   0.0081   0.0125   0.0155   0.0165   0.1055  ±0.62%    61353
   · 1000 refs branch toggle                             12,605.43   0.0284   0.3940   0.0793   0.1205   0.2384   0.2616   0.3062  ±1.59%     6304
   · 1 ref invoking 10 effects                          471,566.11   0.0020   0.5033   0.0021   0.0021   0.0023   0.0026   0.0056  ±0.25%   235784
   · 1 ref invoking 100 effects                          42,111.27   0.0227   0.2647   0.0237   0.0235   0.0296   0.0324   0.0362  ±0.21%    21056
   · 1 ref invoking 1000 effects                          4,574.42   0.2066   0.5334   0.2186   0.2197   0.2460   0.2825   0.3948  ±0.25%     2288
 ✓ packages/reactivity/__benchmarks__/reactiveMap.bench.ts (10) 7509ms
     name                                                                     hz     min      max    mean     p75     p99    p995    p999      rme  samples
   · create reactive map                                            1,231,162.76  0.0002  35.7228  0.0008  0.0004  0.0008  0.0011  0.0037  ±21.43%   621749
   · write reactive map property                                    2,425,444.91  0.0003   0.3901  0.0004  0.0004  0.0009  0.0009  0.0010   ±0.22%  1212723
   · write reactive map, don't read computed (never invoked)        2,461,929.56  0.0003   0.1027  0.0004  0.0004  0.0005  0.0005  0.0005   ±0.06%  1230965   fastest
   · write reactive map, don't read computed (invoked)              1,628,646.86  0.0005   0.5250  0.0006  0.0006  0.0014  0.0014  0.0015   ±0.80%   814324
   · write reactive map, read computed                                754,598.69  0.0011   0.4942  0.0013  0.0013  0.0018  0.0031  0.0033   ±0.58%   377300
   · write reactive map (10'000 items), read computed                   2,111.52  0.4552   1.2053  0.4736  0.4716  0.5137  0.7847  1.1494   ±0.53%     1056
   · write reactive map, don't read 1000 computeds (never invoked)  2,446,731.24  0.0003   0.2741  0.0004  0.0004  0.0005  0.0005  0.0009   ±0.16%  1223366
   · write reactive map, don't read 1000 computeds (invoked)          245,751.29  0.0038   0.5056  0.0041  0.0040  0.0043  0.0052  0.0114   ±0.33%   122876
   · write reactive map, read 1000 computeds                            1,395.38  0.6742   1.7222  0.7167  0.7195  0.8335  1.1573  1.7222   ±0.60%      698   slowest
   · 1000 reactive maps, 1 computed                                   944,869.50  0.0007   1.0043  0.0011  0.0009  0.0010  0.0010  0.0028   ±2.53%   472435
 ✓ packages/reactivity/__benchmarks__/computed.bench.ts (14) 11733ms
   ✓ computed (14) 11731ms
     name                                                                    hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · create computed                                               7,340,029.94  0.0000  1.8098  0.0001  0.0001  0.0003  0.0003  0.0005  ±1.27%  3670015   fastest
   · write independent ref dep                                     5,331,972.72  0.0001  0.2325  0.0002  0.0002  0.0002  0.0003  0.0004  ±0.17%  2665987
   · write ref, don't read computed (without effect)               5,119,762.70  0.0001  0.7784  0.0002  0.0002  0.0003  0.0004  0.0005  ±0.51%  2559882
   · write ref, don't read computed (with effect)                  1,059,180.77  0.0008  0.3091  0.0009  0.0010  0.0010  0.0011  0.0022  ±0.31%   529591
   · write ref, read computed (without effect)                     1,715,141.07  0.0005  0.2351  0.0006  0.0006  0.0006  0.0007  0.0008  ±0.23%   857571
   · write ref, read computed (with effect)                          940,060.40  0.0010  0.3175  0.0011  0.0011  0.0011  0.0012  0.0017  ±0.32%   470031
   · write ref, don't read 1000 computeds (without effect)         5,247,650.62  0.0001  0.6994  0.0002  0.0002  0.0002  0.0002  0.0003  ±0.44%  2623826
   · write ref, don't read 1000 computeds (with multiple effects)      1,268.02  0.7463  1.3504  0.7886  0.7884  1.0037  1.0311  1.3504  ±0.38%      635
   · write ref, don't read 1000 computeds (with single effect)         1,942.81  0.4865  0.7813  0.5147  0.5159  0.6073  0.7114  0.7813  ±0.27%      972
   · write ref, read 1000 computeds (no effect)                        2,578.57  0.3610  0.7409  0.3878  0.3879  0.4442  0.4688  0.6227  ±0.27%     1290
   · write ref, read 1000 computeds (with multiple effects)            1,123.53  0.8424  1.1887  0.8900  0.8913  1.0773  1.1722  1.1887  ±0.32%      562
   · write ref, read 1000 computeds (with single effect)                 933.25  1.0230  1.3531  1.0715  1.0790  1.3134  1.3250  1.3531  ±0.35%      467   slowest
   · 1000 refs, read 1 computed (without effect)                      11,892.95  0.0800  0.3002  0.0841  0.0850  0.0953  0.1020  0.2613  ±0.27%     5947
   · 1000 refs, read 1 computed (with effect)                         11,780.25  0.0810  0.2500  0.0849  0.0863  0.0970  0.1058  0.2115  ±0.22%     5891
 ✓ packages/runtime-core/__tests__/apiWatch.bench.ts (6) 5580ms
     name                                                                      hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · create watcher                                                  1,804,811.65  0.0004  0.9785  0.0006  0.0005  0.0013  0.0014  0.0020  ±0.83%   902406
   · update ref to trigger watcher (scheduled but not executed)      4,503,777.08  0.0001  0.2543  0.0002  0.0002  0.0003  0.0004  0.0005  ±0.19%  2251889
   · update ref to trigger watcher (executed)                        1,342,486.25  0.0006  1.2544  0.0007  0.0007  0.0017  0.0018  0.0024  ±0.87%   671244   slowest
   · create watchEffect                                              2,462,287.26  0.0002  0.7155  0.0004  0.0004  0.0010  0.0010  0.0010  ±0.70%  1231144
   · update ref to trigger watchEffect (scheduled but not executed)  4,505,915.13  0.0001  0.1838  0.0002  0.0002  0.0003  0.0003  0.0005  ±0.12%  2252958   fastest
   · update ref to trigger watchEffect (executed)                    1,423,376.45  0.0006  1.3985  0.0007  0.0007  0.0008  0.0010  0.0036  ±0.77%   711689.66  0.0001  0.0682  0.0002  0.0002  0.0003  0.0003  0.0003  ±0.07%  2349852   slowest
 ✓ packages/reactivity/__benchmarks__/reactiveArray.bench.ts (15) 9971ms
     name                                                                       hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · reduce *reactive* array, 10 elements                                53,741.86  0.0170  3.6785  0.0186  0.0178  0.0434  0.0490  0.1264  ±1.53%    26871
   · reduce *reactive* array, 10 elements, only change first value      148,803.25  0.0063  0.7073  0.0067  0.0066  0.0072  0.0129  0.0223  ±0.41%    74402
   · reduce *readonly* array, 10 elements                                66,735.80  0.0130  2.9992  0.0150  0.0140  0.0193  0.0332  0.1046  ±2.45%    33368
   · reduce *raw* array, copied, 10 elements                          1,703,066.43  0.0005  0.7682  0.0006  0.0006  0.0007  0.0012  0.0013  ±0.57%   851534
   · reduce *raw* array, manually triggered, 10 elements              1,771,019.72  0.0005  0.3467  0.0006  0.0005  0.0009  0.0013  0.0013  ±0.39%   885510   fastest
   · reduce *reactive* array, 100 elements                                5,737.86  0.1678  0.7471  0.1743  0.1705  0.3690  0.4368  0.4961  ±0.64%     2869
   · reduce *reactive* array, 100 elements, only change first value      20,597.08  0.0456  0.3917  0.0486  0.0477  0.0725  0.1155  0.2448  ±0.45%    10299
   · reduce *readonly* array, 100 elements                               10,136.68  0.0921  0.7042  0.0987  0.0968  0.1187  0.2222  0.5746  ±0.84%     5069
   · reduce *raw* array, copied, 100 elements                         1,277,887.62  0.0007  0.2239  0.0008  0.0008  0.0009  0.0010  0.0013  ±0.39%   638944
   · reduce *raw* array, manually triggered, 100 elements             1,409,665.50  0.0006  0.2487  0.0007  0.0007  0.0008  0.0008  0.0012  ±0.30%   704833
   · reduce *reactive* array, 1000 elements                                 601.27  1.6015  1.9243  1.6632  1.6737  1.8958  1.9155  1.9243  ±0.31%      301   slowest
   · reduce *reactive* array, 1000 elements, only change first value      2,218.56  0.4270  0.7411  0.4507  0.4506  0.5726  0.6125  0.6790  ±0.29%     1110
   · reduce *readonly* array, 1000 elements                               1,078.02  0.8668  1.3260  0.9276  0.9218  1.2773  1.2922  1.3260  ±0.66%      540
   · reduce *raw* array, copied, 1000 elements                          365,484.16  0.0024  0.2307  0.0027  0.0027  0.0030  0.0031  0.0070  ±0.35%   182743
   · reduce *raw* array, manually triggered, 1000 elements              423,492.43  0.0022  0.1956  0.0024  0.0024  0.0025  0.0028  0.0073  ±0.13%   211747
 ✓ packages/reactivity/__benchmarks__/effect.bench.ts (18) 13062ms
   ✓ effect (18) 13060ms
     name                                                       hz      min      max     mean      p75      p99     p995     p999     rme  samples
   · single ref invoke                                2,449,582.35   0.0003   0.9198   0.0004   0.0004   0.0005   0.0009   0.0013  ±0.61%  1224792
   · create an effect that tracks 1 refs              2,692,591.38   0.0002   1.4220   0.0004   0.0003   0.0008   0.0009   0.0010  ±0.90%  1346296   fastest
   · create an effect that tracks 10 refs               464,818.21   0.0019   1.0188   0.0022   0.0020   0.0047   0.0047   0.0066  ±0.68%   232410
   · create an effect that tracks 100 refs               47,150.39   0.0187   1.2912   0.0212   0.0193   0.0483   0.0737   0.2648  ±1.20%    23576
   · create an effect that tracks 1000 refs               4,800.80   0.1869   1.8498   0.2083   0.1915   0.5777   0.6299   1.2765  ±1.67%     2401
   · create and stop an effect that tracks 1 refs     2,461,279.41   0.0003   0.3055   0.0004   0.0004   0.0005   0.0007   0.0010  ±0.55%  1230640
   · create and stop an effect that tracks 10 refs      407,741.18   0.0022   0.1988   0.0025   0.0024   0.0028   0.0029   0.0063  ±0.41%   203871
   · create and stop an effect that tracks 100 refs      43,723.89   0.0213   0.2984   0.0229   0.0225   0.0255   0.0360   0.1549  ±0.45%    21862
   · create and stop an effect that tracks 1000 refs      4,395.97   0.2122   0.5280   0.2275   0.2237   0.3581   0.3819   0.4186  ±0.47%     2199
   · 10 refs invoke                                      87,209.50   0.0106   0.2296   0.0115   0.0115   0.0142   0.0196   0.0304  ±0.25%    43605
   · 100 refs invoke                                      1,131.92   0.8294   1.1110   0.8835   0.8908   1.0568   1.0805   1.1110  ±0.31%      566
   · 1000 refs invoke                                      11.2561  87.9438  89.8169  88.8408  89.1844  89.8169  89.8169  89.8169  ±0.47%       10   slowest
   · 10 refs branch toggle                              794,424.68   0.0006   0.1580   0.0013   0.0017   0.0020   0.0021   0.0055  ±0.34%   397213
   · 100 refs branch toggle                             122,703.79   0.0031   0.2428   0.0081   0.0125   0.0155   0.0165   0.1055  ±0.62%    61353
   · 1000 refs branch toggle                             12,605.43   0.0284   0.3940   0.0793   0.1205   0.2384   0.2616   0.3062  ±1.59%     6304
   · 1 ref invoking 10 effects                          471,566.11   0.0020   0.5033   0.0021   0.0021   0.0023   0.0026   0.0056  ±0.25%   235784
   · 1 ref invoking 100 effects                          42,111.27   0.0227   0.2647   0.0237   0.0235   0.0296   0.0324   0.0362  ±0.21%    21056
   · 1 ref invoking 1000 effects                          4,574.42   0.2066   0.5334   0.2186   0.2197   0.2460   0.2825   0.3948  ±0.25%     2288
 ✓ packages/reactivity/__benchmarks__/reactiveMap.bench.ts (10) 7509ms
     name                                                                     hz     min      max    mean     p75     p99    p995    p999      rme  samples
   · create reactive map                                            1,231,162.76  0.0002  35.7228  0.0008  0.0004  0.0008  0.0011  0.0037  ±21.43%   621749
   · write reactive map property                                    2,425,444.91  0.0003   0.3901  0.0004  0.0004  0.0009  0.0009  0.0010   ±0.22%  1212723
   · write reactive map, don't read computed (never invoked)        2,461,929.56  0.0003   0.1027  0.0004  0.0004  0.0005  0.0005  0.0005   ±0.06%  1230965   fastest
   · write reactive map, don't read computed (invoked)              1,628,646.86  0.0005   0.5250  0.0006  0.0006  0.0014  0.0014  0.0015   ±0.80%   814324
   · write reactive map, read computed                                754,598.69  0.0011   0.4942  0.0013  0.0013  0.0018  0.0031  0.0033   ±0.58%   377300
   · write reactive map (10'000 items), read computed                   2,111.52  0.4552   1.2053  0.4736  0.4716  0.5137  0.7847  1.1494   ±0.53%     1056
   · write reactive map, don't read 1000 computeds (never invoked)  2,446,731.24  0.0003   0.2741  0.0004  0.0004  0.0005  0.0005  0.0009   ±0.16%  1223366
   · write reactive map, don't read 1000 computeds (invoked)          245,751.29  0.0038   0.5056  0.0041  0.0040  0.0043  0.0052  0.0114   ±0.33%   122876
   · write reactive map, read 1000 computeds                            1,395.38  0.6742   1.7222  0.7167  0.7195  0.8335  1.1573  1.7222   ±0.60%      698   slowest
   · 1000 reactive maps, 1 computed                                   944,869.50  0.0007   1.0043  0.0011  0.0009  0.0010  0.0010  0.0028   ±2.53%   472435
 ✓ packages/reactivity/__benchmarks__/computed.bench.ts (14) 11733ms
   ✓ computed (14) 11731ms
     name                                                                    hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · create computed                                               7,340,029.94  0.0000  1.8098  0.0001  0.0001  0.0003  0.0003  0.0005  ±1.27%  3670015   fastest
   · write independent ref dep                                     5,331,972.72  0.0001  0.2325  0.0002  0.0002  0.0002  0.0003  0.0004  ±0.17%  2665987
   · write ref, don't read computed (without effect)               5,119,762.70  0.0001  0.7784  0.0002  0.0002  0.0003  0.0004  0.0005  ±0.51%  2559882
   · write ref, don't read computed (with effect)                  1,059,180.77  0.0008  0.3091  0.0009  0.0010  0.0010  0.0011  0.0022  ±0.31%   529591
   · write ref, read computed (without effect)                     1,715,141.07  0.0005  0.2351  0.0006  0.0006  0.0006  0.0007  0.0008  ±0.23%   857571
   · write ref, read computed (with effect)                          940,060.40  0.0010  0.3175  0.0011  0.0011  0.0011  0.0012  0.0017  ±0.32%   470031
   · write ref, don't read 1000 computeds (without effect)         5,247,650.62  0.0001  0.6994  0.0002  0.0002  0.0002  0.0002  0.0003  ±0.44%  2623826
   · write ref, don't read 1000 computeds (with multiple effects)      1,268.02  0.7463  1.3504  0.7886  0.7884  1.0037  1.0311  1.3504  ±0.38%      635
   · write ref, don't read 1000 computeds (with single effect)         1,942.81  0.4865  0.7813  0.5147  0.5159  0.6073  0.7114  0.7813  ±0.27%      972
   · write ref, read 1000 computeds (no effect)                        2,578.57  0.3610  0.7409  0.3878  0.3879  0.4442  0.4688  0.6227  ±0.27%     1290
   · write ref, read 1000 computeds (with multiple effects)            1,123.53  0.8424  1.1887  0.8900  0.8913  1.0773  1.1722  1.1887  ±0.32%      562
   · write ref, read 1000 computeds (with single effect)                 933.25  1.0230  1.3531  1.0715  1.0790  1.3134  1.3250  1.3531  ±0.35%      467   slowest
   · 1000 refs, read 1 computed (without effect)                      11,892.95  0.0800  0.3002  0.0841  0.0850  0.0953  0.1020  0.2613  ±0.27%     5947
   · 1000 refs, read 1 computed (with effect)                         11,780.25  0.0810  0.2500  0.0849  0.0863  0.0970  0.1058  0.2115  ±0.22%     5891
 ✓ packages/runtime-core/__tests__/apiWatch.bench.ts (6) 5580ms
     name                                                                      hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · create watcher                                                  1,804,811.65  0.0004  0.9785  0.0006  0.0005  0.0013  0.0014  0.0020  ±0.83%   902406
   · update ref to trigger watcher (scheduled but not executed)      4,503,777.08  0.0001  0.2543  0.0002  0.0002  0.0003  0.0004  0.0005  ±0.19%  2251889
   · update ref to trigger watcher (executed)                        1,342,486.25  0.0006  1.2544  0.0007  0.0007  0.0017  0.0018  0.0024  ±0.87%   671244   slowest
   · create watchEffect                                              2,462,287.26  0.0002  0.7155  0.0004  0.0004  0.0010  0.0010  0.0010  ±0.70%  1231144
   · update ref to trigger watchEffect (scheduled but not executed)  4,505,915.13  0.0001  0.1838  0.0002  0.0002  0.0003  0.0003  0.0005  ±0.12%  2252958   fastest
   · update ref to trigger watchEffect (executed)                    1,423,376.45  0.0006  1.3985  0.0007  0.0007  0.0008  0.0010  0.0036  ±0.77%   711689
Full benchmarks for this PR
 ✓ packages/reactivity/__benchmarks__/reactiveObject.bench.ts (3) 2911ms
     name                                   hz     min      max    mean     p75     p99    p995    p999      rme  samples
   · create reactive obj          2,061,439.32  0.0001  31.7799  0.0005  0.0003  0.0007  0.0008  0.0031  ±22.04%  1030720
   · read reactive obj property   4,007,959.31  0.0001   0.5573  0.0002  0.0002  0.0005  0.0006  0.0006   ±0.43%  2003980   fastest
   · write reactive obj property  1,634,728.20  0.0005   0.6218  0.0006  0.0006  0.0014  0.0014  0.0014   ±0.34%   817365   slowest
 ✓ packages/reactivity/__benchmarks__/ref.bench.ts (4) 6497ms
   ✓ ref (4) 6496ms
     name                       hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · create ref       7,864,574.06  0.0000  1.1923  0.0001  0.0001  0.0002  0.0003  0.0005  ±0.96%  3932288
   · write ref        5,105,122.61  0.0001  0.3080  0.0002  0.0002  0.0003  0.0004  0.0005  ±0.38%  2552562
   · read ref        14,254,211.80  0.0000  1.1114  0.0001  0.0001  0.0001  0.0001  0.0002  ±0.98%  7127107   fastest
   · write/read ref   4,314,595.02  0.0001  0.9373  0.0002  0.0002  0.0003  0.0005  0.0005  ±0.69%  2157298   slowest
 ✓ packages/reactivity/__benchmarks__/reactiveArray.bench.ts (15) 10808ms
     name                                                                       hz     min      max    mean     p75     p99    p995     p999      rme  samples
   · reduce *reactive* array, 10 elements                                52,496.29  0.0173   0.8698  0.0190  0.0186  0.0433  0.0455   0.1175   ±0.67%    26249
   · reduce *reactive* array, 10 elements, only change first value      136,662.22  0.0070   0.8925  0.0073  0.0072  0.0077  0.0103   0.0270   ±0.47%    68333
   · reduce *readonly* array, 10 elements                                60,380.76  0.0132   8.5446  0.0166  0.0145  0.0349  0.0528   0.1539   ±6.16%    30191
   · reduce *raw* array, copied, 10 elements                          3,449,160.62  0.0002   1.4801  0.0003  0.0003  0.0006  0.0007   0.0007   ±0.96%  1724581
   · reduce *raw* array, manually triggered, 10 elements              4,271,290.00  0.0001   0.0708  0.0002  0.0002  0.0003  0.0003   0.0005   ±0.07%  2135646   fastest
   · reduce *reactive* array, 100 elements                                5,550.13  0.1700   0.5353  0.1802  0.1812  0.2076  0.2241   0.5312   ±0.35%     2776
   · reduce *reactive* array, 100 elements, only change first value      17,908.26  0.0531   0.4997  0.0558  0.0555  0.0651  0.0747   0.1208   ±0.37%     8955
   · reduce *readonly* array, 100 elements                                8,357.87  0.0954  10.5362  0.1196  0.1027  0.2468  0.5196   3.2426   ±6.40%     4215
   · reduce *raw* array, copied, 100 elements                         2,038,818.36  0.0003   0.3737  0.0005  0.0005  0.0006  0.0007   0.0010   ±0.84%  1019410
   · reduce *raw* array, manually triggered, 100 elements             2,546,326.80  0.0002   0.5125  0.0004  0.0004  0.0005  0.0005   0.0009   ±0.61%  1273164
   · reduce *reactive* array, 1000 elements                                 566.23  1.6806   2.8495  1.7661  1.7678  2.1901  2.3754   2.8495   ±0.66%      284   slowest
   · reduce *reactive* array, 1000 elements, only change first value      1,857.91  0.5095   0.9390  0.5382  0.5426  0.5920  0.8986   0.9390   ±0.39%      929
   · reduce *readonly* array, 1000 elements                                 908.82  0.9352  11.5394  1.1003  0.9989  6.1752  6.2693  11.5394   ±7.16%      455
   · reduce *raw* array, copied, 1000 elements                          374,512.77  0.0022  30.5737  0.0027  0.0024  0.0028  0.0043   0.0113  ±12.01%   187257
   · reduce *raw* array, manually triggered, 1000 elements              488,037.65  0.0018   0.7243  0.0020  0.0020  0.0021  0.0022   0.0060   ±0.35%   244019
 ✓ packages/reactivity/__benchmarks__/effect.bench.ts (18) 13892ms
   ✓ effect (18) 13891ms
     name                                                       hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · single ref invoke                                3,417,330.88  0.0002  1.7093  0.0003  0.0003  0.0004  0.0007  0.0008  ±0.91%  1708666   fastest
   · create an effect that tracks 1 refs              3,322,125.21  0.0002  0.8361  0.0003  0.0003  0.0007  0.0007  0.0007  ±0.77%  1661063
   · create an effect that tracks 10 refs               452,534.31  0.0019  1.1979  0.0022  0.0020  0.0048  0.0049  0.0162  ±1.10%   226268
   · create an effect that tracks 100 refs               48,990.71  0.0184  4.3603  0.0204  0.0192  0.0462  0.0503  0.2224  ±2.07%    24496
   · create an effect that tracks 1000 refs               5,077.95  0.1846  0.5903  0.1969  0.1923  0.4223  0.4460  0.5418  ±0.68%     2539
   · create and stop an effect that tracks 1 refs     3,411,302.94  0.0002  0.8195  0.0003  0.0003  0.0005  0.0007  0.0007  ±0.68%  1705652
   · create and stop an effect that tracks 10 refs      462,384.21  0.0019  0.5215  0.0022  0.0021  0.0049  0.0050  0.0102  ±0.52%   231193
   · create and stop an effect that tracks 100 refs      49,559.94  0.0189  1.1464  0.0202  0.0198  0.0225  0.0289  0.1651  ±0.62%    24780
   · create and stop an effect that tracks 1000 refs      4,982.29  0.1886  0.6040  0.2007  0.1983  0.3801  0.4110  0.4563  ±0.53%     2492
   · 10 refs invoke                                      81,384.50  0.0115  0.2260  0.0123  0.0123  0.0140  0.0159  0.0266  ±0.25%    40693
   · 100 refs invoke                                        962.02  0.9920  1.9280  1.0395  1.0412  1.2193  1.2393  1.9280  ±0.45%      482
   · 1000 refs invoke                                       9.5814  103.61  105.82  104.37  104.55  105.82  105.82  105.82  ±0.47%       10   slowest
   · 10 refs branch toggle                            1,216,635.38  0.0003  0.1708  0.0008  0.0013  0.0013  0.0014  0.0017  ±0.27%   608318
   · 100 refs branch toggle                             202,535.51  0.0005  0.2812  0.0049  0.0092  0.0098  0.0128  0.0183  ±0.61%   101269
   · 1000 refs branch toggle                             21,733.14  0.0033  0.2908  0.0460  0.0869  0.0970  0.1005  0.2127  ±1.75%    10867
   · 1 ref invoking 10 effects                        1,028,389.95  0.0008  0.2369  0.0010  0.0010  0.0011  0.0012  0.0050  ±0.27%   514195
   · 1 ref invoking 100 effects                         127,852.14  0.0074  0.2846  0.0078  0.0078  0.0100  0.0120  0.0172  ±0.31%    63927
   · 1 ref invoking 1000 effects                         13,068.23  0.0733  0.2245  0.0765  0.0767  0.0880  0.0936  0.1931  ±0.21%     6535
 ✓ packages/reactivity/__benchmarks__/reactiveMap.bench.ts (10) 7996ms
     name                                                                     hz     min      max    mean     p75     p99    p995    p999      rme  samples
   · create reactive map                                            1,258,845.68  0.0002  34.6945  0.0008  0.0004  0.0008  0.0010  0.0023  ±21.06%   629432
   · write reactive map property                                    2,798,978.85  0.0002   0.7082  0.0004  0.0004  0.0008  0.0008  0.0008   ±0.51%  1399490   fastest
   · write reactive map, don't read computed (never invoked)        2,698,717.95  0.0002   1.0219  0.0004  0.0004  0.0005  0.0008  0.0008   ±0.73%  1349359
   · write reactive map, don't read computed (invoked)              1,845,185.06  0.0004   0.8183  0.0005  0.0005  0.0012  0.0012  0.0013   ±0.84%   922593
   · write reactive map, read computed                              1,040,419.51  0.0008   0.4913  0.0010  0.0010  0.0011  0.0011  0.0016   ±0.65%   520210
   · write reactive map (10'000 items), read computed                   2,143.59  0.4489   0.5500  0.4665  0.4678  0.5047  0.5225  0.5359   ±0.12%     1072   slowest
   · write reactive map, don't read 1000 computeds (never invoked)  2,705,554.44  0.0002   0.4318  0.0004  0.0004  0.0005  0.0005  0.0005   ±0.18%  1352778
   · write reactive map, don't read 1000 computeds (invoked)        1,840,752.44  0.0004   0.2605  0.0005  0.0006  0.0007  0.0007  0.0010   ±0.33%   920377
   · write reactive map, read 1000 computeds                            2,205.95  0.4172   0.6812  0.4533  0.4833  0.5105  0.6486  0.6792   ±0.42%     1104
   · 1000 reactive maps, 1 computed                                 1,026,797.69  0.0006   0.8799  0.0010  0.0007  0.0009  0.0011  0.0048   ±3.08%   513399
 ✓ packages/reactivity/__benchmarks__/computed.bench.ts (14) 12280ms
   ✓ computed (14) 12278ms
     name                                                                    hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · create computed                                               8,485,230.93  0.0000  1.1103  0.0001  0.0001  0.0002  0.0002  0.0005  ±0.87%  4242616   fastest
   · write independent ref dep                                     5,068,537.95  0.0001  0.7742  0.0002  0.0002  0.0003  0.0004  0.0004  ±0.51%  2534270
   · write ref, don't read computed (without effect)               5,052,959.72  0.0001  1.7301  0.0002  0.0002  0.0003  0.0003  0.0004  ±0.84%  2526480
   · write ref, don't read computed (with effect)                  2,222,444.26  0.0003  0.3965  0.0004  0.0005  0.0005  0.0005  0.0006  ±0.46%  1111223
   · write ref, read computed (without effect)                     2,926,053.86  0.0002  0.4450  0.0003  0.0004  0.0004  0.0007  0.0008  ±0.20%  1463027
   · write ref, read computed (with effect)                        2,062,959.90  0.0004  0.2545  0.0005  0.0005  0.0006  0.0006  0.0008  ±0.28%  1031480
   · write ref, don't read 1000 computeds (without effect)         4,398,145.09  0.0001  0.5661  0.0002  0.0002  0.0003  0.0003  0.0004  ±0.23%  2199073
   · write ref, don't read 1000 computeds (with multiple effects)      4,380.04  0.2170  0.5674  0.2283  0.2280  0.2497  0.3964  0.4946  ±0.33%     2191
   · write ref, don't read 1000 computeds (with single effect)         2,636.78  0.2524  0.7919  0.3793  0.5223  0.6169  0.6659  0.7853  ±1.90%     1319
   · write ref, read 1000 computeds (no effect)                        7,440.75  0.1162  0.4709  0.1344  0.1273  0.1860  0.2846  0.4084  ±0.65%     3721
   · write ref, read 1000 computeds (with multiple effects)            3,864.92  0.2432  0.5400  0.2587  0.2594  0.3357  0.4128  0.4623  ±0.30%     1933
   · write ref, read 1000 computeds (with single effect)               2,577.92  0.3628  0.6930  0.3879  0.3882  0.5519  0.6143  0.6748  ±0.36%     1289   slowest
   · 1000 refs, read 1 computed (without effect)                       9,665.87  0.0986  0.3238  0.1035  0.1039  0.1127  0.1156  0.2554  ±0.23%     4833
   · 1000 refs, read 1 computed (with effect)                          9,639.00  0.0985  0.3413  0.1037  0.1053  0.1166  0.1280  0.2734  ±0.27%     4820
 ✓ packages/runtime-core/__tests__/apiWatch.bench.ts (6) 5917ms
     name                                                                      hz     min      max    mean     p75     p99    p995    p999     rme  samples
   · create watcher                                                  2,044,822.58  0.0003   1.5128  0.0005  0.0005  0.0012  0.0012  0.0019  ±0.96%  1022412
   · update ref to trigger watcher (scheduled but not executed)      4,579,089.58  0.0001   0.2180  0.0002  0.0002  0.0004  0.0005  0.0005  ±0.20%  2289545   fastest
   · update ref to trigger watcher (executed)                        1,395,265.01  0.0005  10.4317  0.0007  0.0007  0.0016  0.0017  0.0042  ±4.48%   697633   slowest
   · create watchEffect                                              2,866,746.93  0.0002   1.7325  0.0003  0.0003  0.0005  0.0008  0.0009  ±0.96%  1433374
   · update ref to trigger watchEffect (scheduled but not executed)  4,368,158.52  0.0001   0.2430  0.0002  0.0002  0.0003  0.0003  0.0004  ±0.29%  2184080
   · update ref to trigger watchEffect (executed)                    1,672,500.99  0.0005   2.5010  0.0006  0.0006  0.0008  0.0011  0.0015  ±1.11%   836251

Computed value are now never stale

Previously computed are internally driven by effects. The effects are stopped / made inactive when the owner unmounts, and in 3.4 leading to issues like #10236.

Also, previously during SSR we disable computed effects to avoid tracking overhead. But to avoid cost of repeated computation, we make computeds cached during render, leading to issues like #10069.

With this PR, computeds are now a subscriber type with its own invalidation logic. They can longer be "stopped" and thus can never be stale. During SSR, we use a global mutation version counter to provide a fast path for repeated access.

@yyx990803
Copy link
Member Author

/ecosystem-ci run

@vue-bot
Copy link
Contributor

vue-bot commented Feb 24, 2024

📝 Ran ecosystem CI: Open

suite result latest scheduled
language-tools success success
nuxt success success
pinia success failure
quasar success success
radix-vue success success
router success success
test-utils success success
vant failure success
vite-plugin-vue success success
vitepress success success
vue-i18n success success
vue-macros success success
vuetify success success
vueuse success success
vue-simple-compiler success success

@yyx990803
Copy link
Member Author

yyx990803 commented Feb 25, 2024

The snapshot fail for Vant should be expected - the styles of the components are computed refs and should be updated when the parent container gets more children linked. Previously it just never got updated during SSR and the new behavior is considered more correct.

/cc @chenjiahan

@yyx990803 yyx990803 merged commit 05eb4e0 into minor Feb 25, 2024
6 checks passed
@yyx990803 yyx990803 deleted the reactivity-refactor branch February 25, 2024 08:51
@chenjiahan
Copy link
Contributor

Get it 👌 I will temporarily skip the failed test cases in Vant until the next version of Vue is upgraded.

@nin-jin
Copy link

nin-jin commented Feb 26, 2024

Congratulations! You have finally passed the tests for the absence of unnecessary side effects and your reactivity system is suitable for use in production.

Unfortunately, you have not yet got rid of unnecessary recalculations. @preact/signals is not the best option to follow here.

image
https://mol.hyoo.ru/#!section=bench/bench=reactivity

@yyx990803
Copy link
Member Author

yyx990803 commented Feb 26, 2024

@nin-jin our priority is making improvements while retaining backwards compatibility, not "winning" in arbitrary micro-benchmarks. We will appreciate any constructive feedback, but being a tongue-in-cheek jerk won't help your library get more adoption.

@nin-jin
Copy link

nin-jin commented Feb 26, 2024

Don't be offended by my praise. Accept it with pride. But don't get too cocky. A year ago, in my analysis, I classified your library as unsuitable for production. In this patch, you fixed it. Now I should update my analysis and move you significantly higher. But if you read my series of articles on reactivity, you may understand how to catch up with the leaders without even breaking backward compatibility.

@nin-jin
Copy link

nin-jin commented Feb 26, 2024

They tell me here that you use a double double linked list to implement pub/sub.

image

This is extremely wasteful of memory (from 44 bytes per edge) and heavily fragments it, which increases processor misses past caches.

In a recent article on pub/sub, I talked about how to achieve a more compact storage of edges in single array (16 bytes per edge) while ensuring constant asymptotic of all operations.

image

@johnsoncodehk
Copy link
Member

johnsoncodehk commented Feb 26, 2024

mol.hyoo.ru/#!section=bench/bench=reactivity

@nin-jin The result seems to be based on Vue 3.4.x rather than this PR. I'm also curious as to why 3.4 produces unnecessary recalculations, and I'd be happy to try to fix it. (I am the author of the Vue 3.4 responsive refactoring PR)

@moushicheng
Copy link
Contributor

Hello yyx, I read your pr in detail, thx your this amazing work!
But because of my stupidity, I don't quite understand why double-linked lists improve memory efficiency
i guess use double-linked lists ,It makes it easier to implement certain functions, but what else does it mean , can you give me some.
Can you give me a hint to help me understand,thx.

@OnlyWick
Copy link
Contributor

OnlyWick commented Mar 5, 2024

@moushicheng Try to answer your question(I haven't read this change carefully yet. I plan to spend some time to finish reading the source code of preact/signals first.).

In the current version of Vue, there are many cases of recursive calls, but by using double-linked lists, the number of recursive operations will be reduced. This means that the call stack will be reduced and memory usage will also be significantly improved.

@rschristian
Copy link

rschristian commented Mar 7, 2024

Some of it is likely only relevant to Preact signals, and not necessarily this change here in Vue, but there's a blog article going over why we (Preact) used linked lists (amongst other things) here: https://preactjs.com/blog/signal-boosting

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

Successfully merging this pull request may close these issues.

8 participants