Skip to content

Commit 05eb4e0

Browse files
authored
Refactor reactivity system to use version counting and doubly-linked list tracking (#10397)
Bug fixes close #10236 close #10069 PRs made stale by this one close #10290 close #10354 close #10189 close #9480
1 parent 272ab9f commit 05eb4e0

38 files changed

+1629
-1122
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import { bench, describe } from 'vitest'
2+
import { type ComputedRef, type Ref, computed, effect, ref } from '../src'
3+
4+
describe('computed', () => {
5+
bench('create computed', () => {
6+
computed(() => 100)
7+
})
8+
9+
{
10+
const v = ref(100)
11+
computed(() => v.value * 2)
12+
let i = 0
13+
bench("write ref, don't read computed (without effect)", () => {
14+
v.value = i++
15+
})
16+
}
17+
18+
{
19+
const v = ref(100)
20+
const c = computed(() => {
21+
return v.value * 2
22+
})
23+
effect(() => c.value)
24+
let i = 0
25+
bench("write ref, don't read computed (with effect)", () => {
26+
v.value = i++
27+
})
28+
}
29+
30+
{
31+
const v = ref(100)
32+
const c = computed(() => {
33+
return v.value * 2
34+
})
35+
let i = 0
36+
bench('write ref, read computed (without effect)', () => {
37+
v.value = i++
38+
c.value
39+
})
40+
}
41+
42+
{
43+
const v = ref(100)
44+
const c = computed(() => {
45+
return v.value * 2
46+
})
47+
effect(() => c.value)
48+
let i = 0
49+
bench('write ref, read computed (with effect)', () => {
50+
v.value = i++
51+
c.value
52+
})
53+
}
54+
55+
{
56+
const v = ref(100)
57+
const computeds: ComputedRef<number>[] = []
58+
for (let i = 0, n = 1000; i < n; i++) {
59+
const c = computed(() => {
60+
return v.value * 2
61+
})
62+
computeds.push(c)
63+
}
64+
let i = 0
65+
bench("write ref, don't read 1000 computeds (without effect)", () => {
66+
v.value = i++
67+
})
68+
}
69+
70+
{
71+
const v = ref(100)
72+
const computeds: ComputedRef<number>[] = []
73+
for (let i = 0, n = 1000; i < n; i++) {
74+
const c = computed(() => {
75+
return v.value * 2
76+
})
77+
effect(() => c.value)
78+
computeds.push(c)
79+
}
80+
let i = 0
81+
bench(
82+
"write ref, don't read 1000 computeds (with multiple effects)",
83+
() => {
84+
v.value = i++
85+
},
86+
)
87+
}
88+
89+
{
90+
const v = ref(100)
91+
const computeds: ComputedRef<number>[] = []
92+
for (let i = 0, n = 1000; i < n; i++) {
93+
const c = computed(() => {
94+
return v.value * 2
95+
})
96+
computeds.push(c)
97+
}
98+
effect(() => {
99+
for (let i = 0; i < 1000; i++) {
100+
computeds[i].value
101+
}
102+
})
103+
let i = 0
104+
bench("write ref, don't read 1000 computeds (with single effect)", () => {
105+
v.value = i++
106+
})
107+
}
108+
109+
{
110+
const v = ref(100)
111+
const computeds: ComputedRef<number>[] = []
112+
for (let i = 0, n = 1000; i < n; i++) {
113+
const c = computed(() => {
114+
return v.value * 2
115+
})
116+
computeds.push(c)
117+
}
118+
let i = 0
119+
bench('write ref, read 1000 computeds (no effect)', () => {
120+
v.value = i++
121+
computeds.forEach(c => c.value)
122+
})
123+
}
124+
125+
{
126+
const v = ref(100)
127+
const computeds: ComputedRef<number>[] = []
128+
for (let i = 0, n = 1000; i < n; i++) {
129+
const c = computed(() => {
130+
return v.value * 2
131+
})
132+
effect(() => c.value)
133+
computeds.push(c)
134+
}
135+
let i = 0
136+
bench('write ref, read 1000 computeds (with multiple effects)', () => {
137+
v.value = i++
138+
computeds.forEach(c => c.value)
139+
})
140+
}
141+
142+
{
143+
const v = ref(100)
144+
const computeds: ComputedRef<number>[] = []
145+
for (let i = 0, n = 1000; i < n; i++) {
146+
const c = computed(() => {
147+
return v.value * 2
148+
})
149+
effect(() => c.value)
150+
computeds.push(c)
151+
}
152+
effect(() => {
153+
for (let i = 0; i < 1000; i++) {
154+
computeds[i].value
155+
}
156+
})
157+
let i = 0
158+
bench('write ref, read 1000 computeds (with single effect)', () => {
159+
v.value = i++
160+
computeds.forEach(c => c.value)
161+
})
162+
}
163+
164+
{
165+
const refs: Ref<number>[] = []
166+
for (let i = 0, n = 1000; i < n; i++) {
167+
refs.push(ref(i))
168+
}
169+
const c = computed(() => {
170+
let total = 0
171+
refs.forEach(ref => (total += ref.value))
172+
return total
173+
})
174+
let i = 0
175+
const n = refs.length
176+
bench('1000 refs, read 1 computed (without effect)', () => {
177+
refs[i++ % n].value++
178+
c.value
179+
})
180+
}
181+
182+
{
183+
const refs: Ref<number>[] = []
184+
for (let i = 0, n = 1000; i < n; i++) {
185+
refs.push(ref(i))
186+
}
187+
const c = computed(() => {
188+
let total = 0
189+
refs.forEach(ref => (total += ref.value))
190+
return total
191+
})
192+
effect(() => c.value)
193+
let i = 0
194+
const n = refs.length
195+
bench('1000 refs, read 1 computed (with effect)', () => {
196+
refs[i++ % n].value++
197+
c.value
198+
})
199+
}
200+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { bench, describe } from 'vitest'
2+
import { type Ref, effect, ref } from '../src'
3+
4+
describe('effect', () => {
5+
{
6+
let i = 0
7+
const n = ref(0)
8+
effect(() => n.value)
9+
bench('single ref invoke', () => {
10+
n.value = i++
11+
})
12+
}
13+
14+
function benchEffectCreate(size: number) {
15+
bench(`create an effect that tracks ${size} refs`, () => {
16+
const refs: Ref[] = []
17+
for (let i = 0; i < size; i++) {
18+
refs.push(ref(i))
19+
}
20+
effect(() => {
21+
for (let i = 0; i < size; i++) {
22+
refs[i].value
23+
}
24+
})
25+
})
26+
}
27+
28+
benchEffectCreate(1)
29+
benchEffectCreate(10)
30+
benchEffectCreate(100)
31+
benchEffectCreate(1000)
32+
33+
function benchEffectCreateAndStop(size: number) {
34+
bench(`create and stop an effect that tracks ${size} refs`, () => {
35+
const refs: Ref[] = []
36+
for (let i = 0; i < size; i++) {
37+
refs.push(ref(i))
38+
}
39+
const e = effect(() => {
40+
for (let i = 0; i < size; i++) {
41+
refs[i].value
42+
}
43+
})
44+
e.effect.stop()
45+
})
46+
}
47+
48+
benchEffectCreateAndStop(1)
49+
benchEffectCreateAndStop(10)
50+
benchEffectCreateAndStop(100)
51+
benchEffectCreateAndStop(1000)
52+
53+
function benchWithRefs(size: number) {
54+
let j = 0
55+
const refs: Ref[] = []
56+
for (let i = 0; i < size; i++) {
57+
refs.push(ref(i))
58+
}
59+
effect(() => {
60+
for (let i = 0; i < size; i++) {
61+
refs[i].value
62+
}
63+
})
64+
bench(`1 effect, mutate ${size} refs`, () => {
65+
for (let i = 0; i < size; i++) {
66+
refs[i].value = i + j++
67+
}
68+
})
69+
}
70+
71+
benchWithRefs(10)
72+
benchWithRefs(100)
73+
benchWithRefs(1000)
74+
75+
function benchWithBranches(size: number) {
76+
const toggle = ref(true)
77+
const refs: Ref[] = []
78+
for (let i = 0; i < size; i++) {
79+
refs.push(ref(i))
80+
}
81+
effect(() => {
82+
if (toggle.value) {
83+
for (let i = 0; i < size; i++) {
84+
refs[i].value
85+
}
86+
}
87+
})
88+
bench(`${size} refs branch toggle`, () => {
89+
toggle.value = !toggle.value
90+
})
91+
}
92+
93+
benchWithBranches(10)
94+
benchWithBranches(100)
95+
benchWithBranches(1000)
96+
97+
function benchMultipleEffects(size: number) {
98+
let i = 0
99+
const n = ref(0)
100+
for (let i = 0; i < size; i++) {
101+
effect(() => n.value)
102+
}
103+
bench(`1 ref invoking ${size} effects`, () => {
104+
n.value = i++
105+
})
106+
}
107+
108+
benchMultipleEffects(10)
109+
benchMultipleEffects(100)
110+
benchMultipleEffects(1000)
111+
})

packages/reactivity/__tests__/reactiveArray.bench.ts renamed to packages/reactivity/__benchmarks__/reactiveArray.bench.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { computed, reactive, readonly, shallowRef, triggerRef } from '../src'
33

44
for (let amount = 1e1; amount < 1e4; amount *= 10) {
55
{
6-
const rawArray = []
6+
const rawArray: any[] = []
77
for (let i = 0, n = amount; i < n; i++) {
88
rawArray.push(i)
99
}
@@ -21,7 +21,7 @@ for (let amount = 1e1; amount < 1e4; amount *= 10) {
2121
}
2222

2323
{
24-
const rawArray = []
24+
const rawArray: any[] = []
2525
for (let i = 0, n = amount; i < n; i++) {
2626
rawArray.push(i)
2727
}
@@ -40,7 +40,7 @@ for (let amount = 1e1; amount < 1e4; amount *= 10) {
4040
}
4141

4242
{
43-
const rawArray = []
43+
const rawArray: any[] = []
4444
for (let i = 0, n = amount; i < n; i++) {
4545
rawArray.push(i)
4646
}
@@ -56,7 +56,7 @@ for (let amount = 1e1; amount < 1e4; amount *= 10) {
5656
}
5757

5858
{
59-
const rawArray = []
59+
const rawArray: any[] = []
6060
for (let i = 0, n = amount; i < n; i++) {
6161
rawArray.push(i)
6262
}

packages/reactivity/__tests__/reactiveMap.bench.ts renamed to packages/reactivity/__benchmarks__/reactiveMap.bench.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ bench('create reactive map', () => {
7979

8080
{
8181
const r = reactive(createMap({ a: 1 }))
82-
const computeds = []
82+
const computeds: any[] = []
8383
for (let i = 0, n = 1000; i < n; i++) {
8484
const c = computed(() => {
8585
return r.get('a') * 2
@@ -94,7 +94,7 @@ bench('create reactive map', () => {
9494

9595
{
9696
const r = reactive(createMap({ a: 1 }))
97-
const computeds = []
97+
const computeds: any[] = []
9898
for (let i = 0, n = 1000; i < n; i++) {
9999
const c = computed(() => {
100100
return r.get('a') * 2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { bench } from 'vitest'
2+
import { reactive } from '../src'
3+
4+
bench('create reactive obj', () => {
5+
reactive({ a: 1 })
6+
})
7+
8+
{
9+
const r = reactive({ a: 1 })
10+
bench('read reactive obj property', () => {
11+
r.a
12+
})
13+
}
14+
15+
{
16+
let i = 0
17+
const r = reactive({ a: 1 })
18+
bench('write reactive obj property', () => {
19+
r.a = i++
20+
})
21+
}

packages/reactivity/__tests__/ref.bench.ts renamed to packages/reactivity/__benchmarks__/ref.bench.ts

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ describe('ref', () => {
2626
const v = ref(100)
2727
bench('write/read ref', () => {
2828
v.value = i++
29-
3029
v.value
3130
})
3231
}

0 commit comments

Comments
 (0)