Skip to content

Conversation

@wangshunnn
Copy link
Collaborator

@wangshunnn wangshunnn commented May 24, 2025

Refactor

  • 响应式系统重构:
    • DepsSubs 由之前的 Array 和 Set 数据结构改为双向链表结构(Doubly-linked list),受 Preact Signals 启发。
    • 基于类似图着色传播算法实现 Push-pull hybrid model,受 @reactivelyalien-signals (Vue3.6) 启发。之前是类似 Vue2 的 Push-based Model。提升性能,解决“相等性问题”。
  • 抽离响应式核心部分到新的子包 —— @mpxjs/reactivity,使用 TS 重写:
    • 抽离新包是考虑架构解耦,健壮 Mpx 生态基建。
    • TS 重写是考虑到后续维护友好、类型友好、开发友好。
  • 新增响应式单测、benchmark。
  • @mpxjs/utils 新增类型声明文件(packages/utils/@types),包含之前 @mpxjs/webpack-plugin 内部单独声明的 declare module '@mpxjs/utils' 部分,方便 @mpxjs/reactivity 等 TS 包引入使用。
  • 非侵入式/破坏性重构,向后兼容,不影响上层 API 设计以及上层库 @mpxjs/core 对外调用,用户层使用无感知。
  • CI:根目录采用 lerna run tsc:build 统一管理需要 TS 构建的子包,方便后续扩展。

Performance

和 v2.10.6 版本的性能对比 benchmark 测试如下图所示,性能普遍全面提升,平均为之前的 2x~4x,最为明显的是 create computed(~9x),和 computed/effect 依赖大量 ref 的场景(~4x),以及大量 computed/effect 场景(~3x)。

前者明显提升的另一个原因是之前 computed 创建需要同时实例化一个 RefImpl 和一个 ReactiveEffect,现在仅需实例化一个 ComputedRefImpl

reactivity-benchmark-for-this-PR
More benchmarks for this PR vs. v2.10.6
 ✓ __benchmarks__/reactivity.bench.ts > reactivity benchmark 13607ms
     name                                                                     hz     min     max    mean     p75     p99    p995    p999     rme   samples
   · create computed                                               21,770,565.22  0.0000  0.1245  0.0000  0.0000  0.0001  0.0001  0.0002  ±0.09%  10885283  [9.94x] ⇑
     create computed                                                2,189,229.86  0.0002  8.2277  0.0005  0.0003  0.0006  0.0007  0.0023  ±6.21%   1094615  (baseline)
   · write ref, don't read computed (without effect)               10,807,893.91  0.0000  0.1243  0.0001  0.0001  0.0002  0.0003  0.0003  ±0.15%   5403947  [1.17x] ⇑
     write ref, don't read computed (without effect)                9,264,878.89  0.0000  0.1885  0.0001  0.0001  0.0002  0.0002  0.0003  ±0.14%   4632440  (baseline)
   · write ref, don't read computed (with effect)                   4,342,400.99  0.0001  0.7008  0.0002  0.0003  0.0005  0.0005  0.0006  ±0.51%   2171201  [1.83x] ⇑
     write ref, don't read computed (with effect)                   2,369,266.04  0.0003  1.3953  0.0004  0.0004  0.0011  0.0013  0.0039  ±1.01%   1184634  (baseline)
   · write ref, read computed (without effect)                      5,759,068.69  0.0001  0.0712  0.0002  0.0002  0.0003  0.0003  0.0003  ±0.06%   2879535  [1.82x] ⇑
     write ref, read computed (without effect)                      3,165,287.80  0.0002  5.7341  0.0003  0.0003  0.0004  0.0010  0.0015  ±2.54%   1582644  (baseline)
   · write ref, read computed (with effect)                         4,477,675.74  0.0001  0.0592  0.0002  0.0003  0.0003  0.0003  0.0004  ±0.04%   2238838  [1.88x] ⇑
     write ref, read computed (with effect)                         2,375,472.60  0.0003  0.1391  0.0004  0.0004  0.0006  0.0008  0.0015  ±0.22%   1187737  (baseline)
   · write ref, don't read 1000 computeds (without effect)          7,685,740.88  0.0000  0.0546  0.0001  0.0001  0.0002  0.0002  0.0003  ±0.04%   3842871  [1.17x] ⇑
     write ref, don't read 1000 computeds (without effect)          6,585,490.41  0.0001  0.1553  0.0002  0.0002  0.0002  0.0003  0.0003  ±0.07%   3292746  (baseline)
   · write ref, don't read 1000 computeds (with multiple effects)      11,931.50  0.0817  0.1946  0.0838  0.0837  0.0894  0.0905  0.0976  ±0.06%      5966  [3.24x] ⇑
     write ref, don't read 1000 computeds (with multiple effects)       3,687.57  0.2326  2.0430  0.2712  0.2623  0.4875  0.6559  1.3192  ±1.36%      1847  (baseline)
   · write ref, don't read 1000 computeds (with single effect)         12,382.85  0.0797  0.3807  0.0808  0.0803  0.0856  0.0880  0.1892  ±0.22%      6192  [2.15x] ⇑
     write ref, don't read 1000 computeds (with single effect)          5,756.84  0.1455  3.2653  0.1737  0.1658  0.3537  0.8557  1.1373  ±1.85%      2881  (baseline)
   · write ref, read 1000 computeds (no effect)                        18,489.98  0.0510  0.0721  0.0541  0.0557  0.0591  0.0605  0.0645  ±0.08%      9245  [2.79x] ⇑
     write ref, read 1000 computeds (no effect)                         6,635.03  0.1307  1.1408  0.1507  0.1478  0.2449  0.2706  1.0137  ±0.90%      3318  (baseline)
   · write ref, read 1000 computeds (with multiple effects)            11,142.21  0.0788  0.4256  0.0897  0.0884  0.1257  0.1479  0.2161  ±0.30%      5572  [3.26x] ⇑
     write ref, read 1000 computeds (with multiple effects)             3,421.93  0.2545  2.6755  0.2922  0.2862  0.4819  0.5655  1.3722  ±1.39%      1712  (baseline)
   · write ref, read 1000 computeds (with single effect)                8,354.15  0.1035  0.7691  0.1197  0.1191  0.1523  0.1632  0.1925  ±0.33%      4178  [2.53x] ⇑
     write ref, read 1000 computeds (with single effect)                3,301.05  0.2784  1.4085  0.3029  0.2983  0.4586  0.5016  1.3667  ±0.87%      1651  (baseline)
   · 1000 refs, read 1 computed (without effect)                       22,408.97  0.0335  1.2976  0.0446  0.0426  0.2199  0.2906  0.5021  ±1.61%     11205  [2.09x] ⇑
     1000 refs, read 1 computed (without effect)                       10,720.22  0.0798  1.0688  0.0933  0.0933  0.1207  0.1503  0.3137  ±0.63%      5361  (baseline)
   · 1000 refs, read 1 computed (with effect)                          34,174.15  0.0275  0.2401  0.0293  0.0292  0.0338  0.0352  0.0814  ±0.18%     17088  [4.31x] ⇑
     1000 refs, read 1 computed (with effect)                           7,919.91  0.1067  3.2648  0.1263  0.1201  0.3023  0.3950  1.3248  ±2.13%      3960  (baseline)

 ✓ __benchmarks__/reactivity.bench.ts > reactivity benchmark 13589ms
     name                                                                     hz     min     max    mean     p75     p99    p995    p999     rme   samples
   · create computed                                               21,709,952.96  0.0000  0.0865  0.0000  0.0000  0.0001  0.0001  0.0002  ±0.08%  10854978  [9.92x] ⇑
     create computed                                                2,189,229.86  0.0002  8.2277  0.0005  0.0003  0.0006  0.0007  0.0023  ±6.21%   1094615  (baseline)
   · write ref, don't read computed (without effect)               11,075,474.56  0.0000  0.0525  0.0001  0.0001  0.0002  0.0002  0.0002  ±0.03%   5537738  [1.20x] ⇑
     write ref, don't read computed (without effect)                9,264,878.89  0.0000  0.1885  0.0001  0.0001  0.0002  0.0002  0.0003  ±0.14%   4632440  (baseline)
   · write ref, don't read computed (with effect)                   4,493,788.91  0.0001  0.0553  0.0002  0.0003  0.0003  0.0003  0.0004  ±0.03%   2246895  [1.90x] ⇑
     write ref, don't read computed (with effect)                   2,369,266.04  0.0003  1.3953  0.0004  0.0004  0.0011  0.0013  0.0039  ±1.01%   1184634  (baseline)
   · write ref, read computed (without effect)                      5,401,377.64  0.0001  0.1857  0.0002  0.0002  0.0003  0.0003  0.0003  ±0.11%   2700689  [1.71x] ⇑
     write ref, read computed (without effect)                      3,165,287.80  0.0002  5.7341  0.0003  0.0003  0.0004  0.0010  0.0015  ±2.54%   1582644  (baseline)
   · write ref, read computed (with effect)                         4,520,859.38  0.0001  0.0934  0.0002  0.0002  0.0003  0.0003  0.0004  ±0.06%   2260430  [1.90x] ⇑
     write ref, read computed (with effect)                         2,375,472.60  0.0003  0.1391  0.0004  0.0004  0.0006  0.0008  0.0015  ±0.22%   1187737  (baseline)
   · write ref, don't read 1000 computeds (without effect)          6,844,584.48  0.0001  0.1929  0.0001  0.0002  0.0002  0.0003  0.0003  ±0.10%   3422293  [1.04x] ⇑
     write ref, don't read 1000 computeds (without effect)          6,585,490.41  0.0001  0.1553  0.0002  0.0002  0.0002  0.0003  0.0003  ±0.07%   3292746  (baseline)
   · write ref, don't read 1000 computeds (with multiple effects)      12,135.56  0.0793  0.1039  0.0824  0.0829  0.0876  0.0885  0.0925  ±0.04%      6068  [3.29x] ⇑
     write ref, don't read 1000 computeds (with multiple effects)       3,687.57  0.2326  2.0430  0.2712  0.2623  0.4875  0.6559  1.3192  ±1.36%      1847  (baseline)
   · write ref, don't read 1000 computeds (with single effect)         12,897.96  0.0763  0.1465  0.0775  0.0775  0.0821  0.0830  0.0897  ±0.04%      6449  [2.24x] ⇑
     write ref, don't read 1000 computeds (with single effect)          5,756.84  0.1455  3.2653  0.1737  0.1658  0.3537  0.8557  1.1373  ±1.85%      2881  (baseline)
   · write ref, read 1000 computeds (no effect)                        19,956.12  0.0478  0.0733  0.0501  0.0513  0.0547  0.0564  0.0591  ±0.07%      9979  [3.01x] ⇑
     write ref, read 1000 computeds (no effect)                         6,635.03  0.1307  1.1408  0.1507  0.1478  0.2449  0.2706  1.0137  ±0.90%      3318  (baseline)
   · write ref, read 1000 computeds (with multiple effects)            11,155.07  0.0879  0.1085  0.0896  0.0897  0.0945  0.0952  0.0972  ±0.02%      5578  [3.26x] ⇑
     write ref, read 1000 computeds (with multiple effects)             3,421.93  0.2545  2.6755  0.2922  0.2862  0.4819  0.5655  1.3722  ±1.39%      1712  (baseline)
   · write ref, read 1000 computeds (with single effect)                8,587.46  0.1148  0.1260  0.1164  0.1167  0.1209  0.1219  0.1239  ±0.02%      4294  [2.60x] ⇑
     write ref, read 1000 computeds (with single effect)                3,301.05  0.2784  1.4085  0.3029  0.2983  0.4586  0.5016  1.3667  ±0.87%      1651  (baseline)
   · 1000 refs, read 1 computed (without effect)                       25,091.32  0.0316  0.2643  0.0399  0.0411  0.0636  0.0745  0.0977  ±0.29%     12546  [2.34x] ⇑
     1000 refs, read 1 computed (without effect)                       10,720.22  0.0798  1.0688  0.0933  0.0933  0.1207  0.1503  0.3137  ±0.63%      5361  (baseline)
   · 1000 refs, read 1 computed (with effect)                          34,442.11  0.0252  0.2235  0.0290  0.0291  0.0376  0.0430  0.0560  ±0.15%     17222  [4.35x] ⇑
     1000 refs, read 1 computed (with effect)                           7,919.91  0.1067  3.2648  0.1263  0.1201  0.3023  0.3950  1.3248  ±2.13%      3960  (baseline)

Memory Usage

内存测试 benchmark 中创建了 1 ref + 10000 computeds (100 chained pairs) + 10000 effects (1 computed with an effect),按树形拓扑结构连接(详见代码或下面折叠内容)。

  • Before (v2.10.6): 19125.24 KB
  • Now (this PR): 5443.71 KB (-72%)
Memory benchmark 拓扑结构
  /**
   * 测试用例中的响应式树形拓扑结构如下:
   *
   *            src(ref)
   *          /    |     \
   * computed1  computed1  computed1 ...  (w columns)
   *    |         |         |
   * computed2  computed2  computed2 ...
   *    |         |         |
   *    .         .         .
   *    .         .         . (h rows)
   *    |         |         |
   * computedH  computedH  computedH ...
   *
   * w = h = 100
   * 每个 computed 还对应一个 effect
   * 所有节点包括: 1 ref + 100 * 100 computed + 100 * 100 effects
   */

Analyse

空间复杂度方面,一个明显优化在依赖收集阶段:

  • Before: 一个 Dep 需要一个 Sub[] 数组,而一个 Sub 则需要记录新旧依赖的两个 Dep[] 数组,以及两个记录新旧依赖 id 编号的 Set(),用于去重和后续查询清理。
  • Now: 一个 Dep 和 一个 Sub 仅靠一个中间节点 link 通过双向链表关联,也无需额外 id 编号,各自仅需头尾节点即可实现链式查询和遍历。

时间复杂度方面,一个明显的优化在于 computed/effect 每次运行后的依赖清理阶段:

  • Before: 需要遍历 deps[] 数组然后对比旧数组来筛选移除,并且 remove 涉及数组 index.ofsplice 操作,所以当不需要移除任何依赖时时间复杂度最优为 $O(N)$,需要移除所有依赖时时间复杂度最坏为 $O(N*M)$,其中 $N$Subdeps[] 数组长度, $M$Depsubs[] 数组长度。
  • Now: 结合双向链表,依赖收集结束后 Sub 尾节点 depsTail 之后的节点便是需要移除的,而且双向链表 remove 单个节点仅需 $O(1)$,所以时间复杂度最优仅为 $O(1)$,最坏为 $O(N)$,其中 $N$Subdeps 数量。相比之前平均降低了一个量级。

Fix and others

修复一些小程序和 Web (Vue2) 表现不一致等历史遗留问题:

  • difftriggerRef):triggerRef 之前仅对 shallowRef 生效,现在对普通 Ref 也生效,与 Web(Vue) 端对齐。
  • difftoRef):修复 toRef 缺少的第三个可选默认值参数,与当前类型声明以及 Web 端对齐。
  • difftoRefs):增加对数组类型的支持,与 Web(Vue) 端对齐。
  • diffeffectScope): 修复嵌套的 scope.off()getCurrentScope 失效问题 - refer Vue fix

wangshunnn added 30 commits May 16, 2025 15:40
@wangshunnn
Copy link
Collaborator Author

内部讨论后续 Mpx3 倾向于接入 @vue/reactivity 核心包,减少未来维护成本,也能提高跨端输出一致性。
本 PR 作为前期实践暂时转为 Draft 存档。

@wangshunnn wangshunnn marked this pull request as draft May 27, 2025 02:52
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.

2 participants