Skip to content

Commit a8e8dd0

Browse files
authored
fix: performance with subscription (#1156)
* test: performance * wip: revert #1153 change * repeat more * make it looser * add debug log * tune * Revert "wip: revert #1153 change" This reverts commit a51dd4d. * fix it * refactor it
1 parent e5f5604 commit a8e8dd0

File tree

2 files changed

+85
-4
lines changed

2 files changed

+85
-4
lines changed

src/vanilla.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ const proxyStateMap: WeakMap<ProxyObject, ProxyState> = new WeakMap()
163163
const refSet: WeakSet<object> = new WeakSet()
164164
const snapCache: WeakMap<object, [version: number, snap: unknown]> =
165165
new WeakMap()
166-
const versionHolder = [1, 1] as [number, number]
166+
const versionHolder = [1] as [number]
167167
const proxyCache: WeakMap<object, ProxyObject> = new WeakMap()
168168

169169
// internal functions
@@ -194,12 +194,12 @@ export function proxy<T extends object>(baseObject: T = {} as T): T {
194194
const listeners = new Set<Listener>()
195195
const notifyUpdate = (op: Op, nextVersion = ++versionHolder[0]) => {
196196
if (version !== nextVersion) {
197-
version = nextVersion
197+
checkVersion = version = nextVersion
198198
listeners.forEach((listener) => listener(op, nextVersion))
199199
}
200200
}
201-
let checkVersion = versionHolder[1]
202-
const ensureVersion = (nextCheckVersion = ++versionHolder[1]) => {
201+
let checkVersion = version
202+
const ensureVersion = (nextCheckVersion = versionHolder[0]) => {
203203
if (checkVersion !== nextCheckVersion) {
204204
checkVersion = nextCheckVersion
205205
propProxyStates.forEach(([propProxyState]) => {

tests/performance.test.tsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { proxy, snapshot, subscribe } from 'valtio'
3+
4+
const DEPTHS = [4, 8, 16, 32, 64, 128, 256]
5+
const REPEATS = 30000
6+
7+
const measurePerformance = (
8+
setUp: () => void,
9+
action: () => void,
10+
tearDown: () => void,
11+
) => {
12+
const times: number[] = []
13+
setUp()
14+
for (let i = 0; i < REPEATS; i++) {
15+
const start = performance.now()
16+
action()
17+
times.push(performance.now() - start)
18+
}
19+
tearDown()
20+
times.slice().sort((a, b) => a - b)
21+
const mid = Math.floor(times.length / 2)
22+
const median =
23+
times.length % 2 ? times[mid]! : (times[mid - 1]! + times[mid]!) / 2
24+
return median
25+
}
26+
27+
const logSlope = (xs: number[], ys: number[]) => {
28+
// slope of log(ys) ~ m * log(xs) + b ; m≈0 means O(1), m≈1 means O(n)
29+
const lx = xs.map(Math.log)
30+
const ly = ys.map(Math.log)
31+
const mean = (a: number[]) => a.reduce((s, v) => s + v, 0) / a.length
32+
const mx = mean(lx),
33+
my = mean(ly)
34+
let num = 0,
35+
den = 0
36+
for (let i = 0; i < lx.length; i++) {
37+
num += (lx[i]! - mx) * (ly[i]! - my)
38+
den += (lx[i]! - mx) ** 2
39+
}
40+
return num / den
41+
}
42+
43+
const buildNestedObj = (depth: number) => {
44+
const leaf = { x: 1 }
45+
let obj: { child: unknown } = { child: leaf }
46+
for (let i = 1; i < depth; i++) {
47+
obj = { child: obj }
48+
}
49+
return { obj, leaf }
50+
}
51+
52+
describe('performance with nested objects', () => {
53+
it('snapshot with subscription', async () => {
54+
const medians: number[] = []
55+
for (const depth of DEPTHS) {
56+
let unsub: (() => void) | undefined
57+
let proxyObj: object | undefined
58+
const median = measurePerformance(
59+
() => {
60+
const { obj, leaf } = buildNestedObj(depth)
61+
const proxyLeaf = proxy(leaf)
62+
proxyObj = proxy(obj)
63+
unsub = subscribe(proxyObj, () => {})
64+
snapshot(proxyObj)
65+
proxyLeaf.x++
66+
},
67+
() => {
68+
snapshot(proxyObj!)
69+
},
70+
() => {
71+
unsub?.()
72+
},
73+
)
74+
medians.push(median)
75+
}
76+
const slope = logSlope(DEPTHS, medians)
77+
expect(slope).toBeLessThan(0.25)
78+
})
79+
80+
// TODO add more performance tests
81+
})

0 commit comments

Comments
 (0)