Skip to content

Commit 22bf390

Browse files
committed
perf: add per-run dep cursors without mutating tails
1 parent 07d790d commit 22bf390

1 file changed

Lines changed: 48 additions & 12 deletions

File tree

packages/rescript-signals/src/signals/Scheduler.res

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ let currentObserver: ref<option<Core.observer>> = ref(None)
77
// Current dependency tracking version (shared across nested compute/effect runs)
88
let currentTrackingVersion: ref<int> = ref(0)
99

10+
// Per-run dependency cursors (separate from true tail pointers).
11+
let currentComputedDepCursor: ref<option<Core.link>> = ref(None)
12+
let currentObserverDepCursor: ref<option<Core.link>> = ref(None)
13+
1014
// Pending effects to execute
1115
let pendingEffects: array<Core.observer> = []
1216
// Pending computeds to recompute (subs that are dirty)
@@ -44,20 +48,22 @@ let trackDepFromComputed = (computedSubs: Core.subs, sourceSubs: Core.subs): uni
4448
newLink.lastTrackedVersion = currentTrackingVersion.contents
4549
Core.linkToSubsDeps(computedSubs, newLink)
4650
Core.linkToSubs(sourceSubs, newLink)
51+
currentComputedDepCursor := Some(newLink)
4752
} else {
4853
let currentVersion = currentTrackingVersion.contents
49-
// Fast path: reuse tail or tail.next to avoid scanning in common cases
54+
// Fast path: reuse run cursor or cursor.next to avoid scanning in common cases.
5055
let fastPathFound = ref(false)
51-
switch computedSubs.lastDep {
52-
| Some(lastDep) =>
53-
if lastDep.subs === sourceSubs {
54-
lastDep.lastTrackedVersion = currentVersion
56+
switch currentComputedDepCursor.contents {
57+
| Some(cursor) =>
58+
if cursor.subs === sourceSubs {
59+
cursor.lastTrackedVersion = currentVersion
5560
fastPathFound.contents = true
5661
} else {
57-
switch lastDep.nextDep {
62+
switch cursor.nextDep {
5863
| Some(nextDep) =>
5964
if nextDep.subs === sourceSubs {
6065
nextDep.lastTrackedVersion = currentVersion
66+
currentComputedDepCursor := Some(nextDep)
6167
fastPathFound.contents = true
6268
}
6369
| None => ()
@@ -71,6 +77,7 @@ let trackDepFromComputed = (computedSubs: Core.subs, sourceSubs: Core.subs): uni
7177
| Some(lastSubLink) =>
7278
if lastSubLink.lastTrackedVersion === currentVersion && lastSubLink.observer === computedObserver {
7379
lastSubLink.lastTrackedVersion = currentVersion
80+
currentComputedDepCursor := Some(lastSubLink)
7481
fastPathFound.contents = true
7582
}
7683
| None => ()
@@ -80,12 +87,14 @@ let trackDepFromComputed = (computedSubs: Core.subs, sourceSubs: Core.subs): uni
8087
if !fastPathFound.contents {
8188
// Fall back to full scan
8289
let found = ref(false)
90+
let foundLink: ref<option<Core.link>> = ref(None)
8391
let link = ref(computedSubs.firstDep)
8492
while link.contents !== None && !found.contents {
8593
switch link.contents {
8694
| Some(l) =>
8795
if l.subs === sourceSubs {
8896
l.lastTrackedVersion = currentVersion
97+
foundLink := Some(l)
8998
found := true
9099
} else {
91100
link := l.nextDep
@@ -100,6 +109,9 @@ let trackDepFromComputed = (computedSubs: Core.subs, sourceSubs: Core.subs): uni
100109
newLink.lastTrackedVersion = currentVersion
101110
Core.linkToSubsDeps(computedSubs, newLink)
102111
Core.linkToSubs(sourceSubs, newLink)
112+
currentComputedDepCursor := Some(newLink)
113+
} else {
114+
currentComputedDepCursor := foundLink.contents
103115
}
104116
}
105117
}
@@ -113,20 +125,22 @@ let trackDepFromEffect = (observer: Core.observer, sourceSubs: Core.subs): unit
113125
newLink.lastTrackedVersion = currentTrackingVersion.contents
114126
Core.linkToDeps(observer, newLink)
115127
Core.linkToSubs(sourceSubs, newLink)
128+
currentObserverDepCursor := Some(newLink)
116129
} else {
117130
let currentVersion = currentTrackingVersion.contents
118-
// Fast path: reuse tail or tail.next to avoid scanning in common cases
131+
// Fast path: reuse run cursor or cursor.next to avoid scanning in common cases.
119132
let fastPathFound = ref(false)
120-
switch observer.lastDep {
121-
| Some(lastDep) =>
122-
if lastDep.subs === sourceSubs {
123-
lastDep.lastTrackedVersion = currentVersion
133+
switch currentObserverDepCursor.contents {
134+
| Some(cursor) =>
135+
if cursor.subs === sourceSubs {
136+
cursor.lastTrackedVersion = currentVersion
124137
fastPathFound.contents = true
125138
} else {
126-
switch lastDep.nextDep {
139+
switch cursor.nextDep {
127140
| Some(nextDep) =>
128141
if nextDep.subs === sourceSubs {
129142
nextDep.lastTrackedVersion = currentVersion
143+
currentObserverDepCursor := Some(nextDep)
130144
fastPathFound.contents = true
131145
}
132146
| None => ()
@@ -140,6 +154,7 @@ let trackDepFromEffect = (observer: Core.observer, sourceSubs: Core.subs): unit
140154
| Some(lastSubLink) =>
141155
if lastSubLink.lastTrackedVersion === currentVersion && lastSubLink.observer === observer {
142156
lastSubLink.lastTrackedVersion = currentVersion
157+
currentObserverDepCursor := Some(lastSubLink)
143158
fastPathFound.contents = true
144159
}
145160
| None => ()
@@ -148,12 +163,14 @@ let trackDepFromEffect = (observer: Core.observer, sourceSubs: Core.subs): unit
148163

149164
if !fastPathFound.contents {
150165
let found = ref(false)
166+
let foundLink: ref<option<Core.link>> = ref(None)
151167
let link = ref(observer.firstDep)
152168
while link.contents !== None && !found.contents {
153169
switch link.contents {
154170
| Some(l) =>
155171
if l.subs === sourceSubs {
156172
l.lastTrackedVersion = currentVersion
173+
foundLink := Some(l)
157174
found := true
158175
} else {
159176
link := l.nextDep
@@ -168,6 +185,9 @@ let trackDepFromEffect = (observer: Core.observer, sourceSubs: Core.subs): unit
168185
newLink.lastTrackedVersion = currentVersion
169186
Core.linkToDeps(observer, newLink)
170187
Core.linkToSubs(sourceSubs, newLink)
188+
currentObserverDepCursor := Some(newLink)
189+
} else {
190+
currentObserverDepCursor := foundLink.contents
171191
}
172192
}
173193
}
@@ -248,7 +268,9 @@ let runComputedCycle = (subs: Core.subs, ~clearPending: bool): unit => {
248268
}
249269

250270
let prev = currentComputedSubs.contents
271+
let prevCursor = currentComputedDepCursor.contents
251272
currentComputedSubs := Some(subs)
273+
currentComputedDepCursor := subs.firstDep
252274

253275
try {
254276
switch subs.compute {
@@ -301,10 +323,12 @@ let runComputedCycle = (subs: Core.subs, ~clearPending: bool): unit => {
301323
}
302324

303325
currentComputedSubs := prev
326+
currentComputedDepCursor := prevCursor
304327
currentTrackingVersion.contents = previousTrackingVersion
305328
} catch {
306329
| exn =>
307330
currentComputedSubs := prev
331+
currentComputedDepCursor := prevCursor
308332
currentTrackingVersion.contents = previousTrackingVersion
309333
throw(exn)
310334
}
@@ -333,7 +357,9 @@ let retrackEffect = (observer: Core.observer): unit => {
333357
Core.clearPending(observer)
334358

335359
let prev = currentObserver.contents
360+
let prevCursor = currentObserverDepCursor.contents
336361
currentObserver := Some(observer)
362+
currentObserverDepCursor := observer.firstDep
337363

338364
try {
339365
observer.run()
@@ -356,10 +382,12 @@ let retrackEffect = (observer: Core.observer): unit => {
356382

357383
Core.clearDirty(observer)
358384
currentObserver := prev
385+
currentObserverDepCursor := prevCursor
359386
currentTrackingVersion.contents = previousTrackingVersion
360387
} catch {
361388
| exn =>
362389
currentObserver := prev
390+
currentObserverDepCursor := prevCursor
363391
currentTrackingVersion.contents = previousTrackingVersion
364392
throw(exn)
365393
}
@@ -508,17 +536,25 @@ let batch = fn => {
508536
let untrack = (fn: unit => 'a): 'a => {
509537
let prevComputed = currentComputedSubs.contents
510538
let prevObserver = currentObserver.contents
539+
let prevComputedCursor = currentComputedDepCursor.contents
540+
let prevObserverCursor = currentObserverDepCursor.contents
511541
currentComputedSubs := None
512542
currentObserver := None
543+
currentComputedDepCursor := None
544+
currentObserverDepCursor := None
513545
try {
514546
let result = fn()
515547
currentComputedSubs := prevComputed
516548
currentObserver := prevObserver
549+
currentComputedDepCursor := prevComputedCursor
550+
currentObserverDepCursor := prevObserverCursor
517551
result
518552
} catch {
519553
| exn =>
520554
currentComputedSubs := prevComputed
521555
currentObserver := prevObserver
556+
currentComputedDepCursor := prevComputedCursor
557+
currentObserverDepCursor := prevObserverCursor
522558
throw(exn)
523559
}
524560
}

0 commit comments

Comments
 (0)