perf(matrix): use DoublyLinkedList + side-map for pending cell changes#27422
perf(matrix): use DoublyLinkedList + side-map for pending cell changes#27422anthony-murphy wants to merge 4 commits into
Conversation
|
Hi! Thank you for opening this PR. Want me to review it? Based on the diff (131 lines, 2 files), I've queued these reviewers:
How this works
|
| const change = pendingCell.local.pop(); | ||
| const change = pendingCell.local.pop()?.data; | ||
| assert(change?.localSeq === setMetadata.localSeq, 0xbaa /* must have change */); | ||
| pendingCell.localByLocalSeq.delete(setMetadata.localSeq); |
There was a problem hiding this comment.
Deep Review: pop() here is correct — rollback is LIFO, the operation being rolled back is always the tail, and assert 0xbaa enforces the match. But on #24604 the Copilot bot flagged this exact construct ("Using pop() to remove the pending operation may remove the most recently added op rather than the one corresponding to the rollback"), and now that this PR adds the localByLocalSeq index — making targeted removal trivial and applying it in reSubmitCore — a reader will reasonably ask why rollback didn't switch too. Switching would be semantically wrong (rollback must restore the previous cell value, which requires popping the tail — see matrix.rollback.spec.ts:163-187, :232-290), so the code is right; only the intent is implicit.
A one-line comment above the pop() pre-empts the same #24604 round-trip.
| pendingCell.localByLocalSeq.delete(setMetadata.localSeq); | |
| // Rollback is LIFO: the operation being rolled back is always the tail. `pop()` is correct; assert 0xbaa enforces the match. | |
| const change = pendingCell.local.pop()?.data; |
Deep ReviewReviewed commit Readiness: 8/10 — ALMOST READY Faithful, well-scoped perf prep: Path to Ready
Context for Reviewers
For human reviewer
Review history (3 prior reviews)
|
Description
Replaces
PendingCellChanges<T>.local: { localSeq, value }[]withDoublyLinkedList<{ localSeq, value }>and adds a sibling side-maplocalByLocalSeq: Map<number, ListNode<{ localSeq, value }>>for O(1)findIndex-by-localSeqlookup.Sites converted:
sendSetCellOppush: insert into list, register node in side-map.reSubmitCore: wasfindIndex + spliceper pending op; nowlocalByLocalSeq.get(localSeq)→list.remove(node)→localByLocalSeq.delete(localSeq). O(1).list.shift()?.data+ side-map delete.pop()+ peek-taillist.last!.data.value.list.length.Why
For a hot cell with k pending writes, the previous reSubmit-per-op was O(k) find + O(k) splice (O(k²) total) and per-ACK was O(k) shift. For n ops to a single hot cell, the whole rebase pass was O(n²). The list-plus-side-map makes every per-op step O(1).
Notes
Snapshot/wire format unaffected (
_pendingCliSeqDataisunknown). Pure perf prep — noreSubmitSquashed, no squash logic, no tests touched, no api-report changes.