Skip to content

Commit 2ee9de4

Browse files
committed
chore: update perf.md
1 parent 64b3642 commit 2ee9de4

1 file changed

Lines changed: 44 additions & 12 deletions

File tree

‎perf.md‎

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,10 @@ Typecheck verified clean after the sweep (`pnpm tsc --noEmit` passes).
9494
## Progress
9595

9696
- **Total findings:** 61
97-
- **Done `[x]`:** 20
98-
- **Partial `[~]`:** 2
97+
- **Done `[x]`:** 21
98+
- **Partial `[~]`:** 3
9999
- **Skipped `[-]`:** 5
100-
- **Not started `[ ]`:** 34
100+
- **Not started `[ ]`:** 32
101101

102102
_(Update these counters as you go.)_
103103

@@ -1697,8 +1697,8 @@ return Array.from({ length: pageCount }, (_, i) => i)
16971697

16981698
## 46. `table_toggleAllRowsSelected` clones entire selection on deselect — Score: 3
16991699

1700-
**Status:** `[ ]` not started
1701-
**Implementation note:** _(none)_
1700+
**Status:** `[~]` partial
1701+
**Implementation note:** Added an opt-in `opts.deselectAll` argument to `table_toggleAllRowsSelected` / `table_toggleAllPageRowsSelected`. When the resolved value is a deselect and `deselectAll` is set, the function returns a fresh empty map in O(1) instead of spreading `old` and `delete`-ing each id. The default path is intentionally unchanged (spread + per-id delete) because an unconditional `{}` return is a **breaking** behavior change — it also drops selected ids that are absent from the current pre-grouped model (e.g. filtered-out rows), which v8 preserved. So the spread/delete cost is only avoided when the caller opts in. Tests added in `rowSelectionFeature.test.ts` (`deselectAll: true` clears an out-of-model id; default preserves it). The behavior change is noted in the framework migration guides.
17021702

17031703
**Location:** `src/features/row-selection/rowSelectionFeature.utils.ts:78–107`
17041704
**Category:** `micro`
@@ -1718,17 +1718,49 @@ When deselecting all, the function spreads `old`, then `delete`s every row id. J
17181718

17191719
---
17201720

1721-
## 47. `table_getIsAllRowsSelected` / `getIsAllPageRowsSelected` flow cleanup — Score: 2
1721+
## 47. Table-level selection getters not memoized + per-row atom re-reads — Score: 7
17221722

1723-
**Status:** `[ ]` not started
1724-
**Implementation note:** _(none)_
1723+
**Status:** `[x]` done
1724+
**Implementation note:** Several changes landed together (motivated by a profiled hang: a select-all checkbox column over 500k rows re-running selection scans on every virtualizer scroll frame):
17251725

1726-
**Location:** `src/features/row-selection/rowSelectionFeature.utils.ts:247–300`
1727-
**Category:** `micro`
1726+
- **Memoized the four table getters** `getIsAllRowsSelected`, `getIsAllPageRowsSelected`, `getIsSomeRowsSelected`, `getIsSomePageRowsSelected`, and added a new memoized `getSelectedRowIds` primitive. Previously these had **no `memoDeps`**, and `memo()` with no deps recomputes on _every_ call — so the header "select all" checkbox re-ran an O(filtered-rows) `.some()` scan on every render. Deps are `rowSelection` + the relevant row model (`getFilteredRowModel()` / `getPaginatedRowModel()`), plus `table.options.enableRowSelection` on the three getters that consult `row_getCanSelect`. `getIsSomeRowsSelected` depends on `rowSelection` only (it delegates to `getSelectedRowIds`).
1727+
- **Eliminated per-row atom re-reads** in the scans. `isRowSelected` now takes the already-fetched `rowSelection` map as a parameter instead of calling `table.atoms.rowSelection.get()` once per row. The single fetched map is threaded through `getIsAllRowsSelected`, `getIsAllPageRowsSelected`, `selectRowsFn`, and `isSubRowSelected`; the redundant `isRowIdSelected` helper was removed.
1728+
- **Routed internal calls through `callMemoOrStaticFn`** so they hit the instance memo, and fixed a key bug: one call site passed the prefixed name `'table_getIsAllPageRowsSelected'`, which never matches the stripped instance method `getIsAllPageRowsSelected`, so it silently fell back to the un-memoized static fn every time.
1729+
- **Simplified `getIsSome*`** to "≥1 selected" (delegating to `getSelectedRowIds`). This is a deliberate behavior change from v8's "some but not all" semantics; documented in the migration guides. The three `get*SelectedRowModel` getters reuse the same emptiness short-circuit via `getIsSomeRowsSelected`.
1730+
- `RowSelectionState` narrowed to `Record<string, true>` (delete-on-deselect invariant), letting `getSelectedRowIds` use a bare `Object.keys`.
1731+
- The originally-proposed `return !preGroupedFlatRows.some(...)` flow tweak was **not** the win and was not adopted; the `let/if/return` shape is retained for the empty-selection short-circuit. Tests added asserting the getters memoize (a `vi.fn` `enableRowSelection` call-count probe) and invalidate on selection change.
17281732

1729-
Replace `let isAll = …; if (cond) isAll = false; return isAll` with `return !preGroupedFlatRows.some(...)`. Engine inlining better.
1733+
**Location:** `src/features/row-selection/rowSelectionFeature.ts:120–155`, `rowSelectionFeature.utils.ts` (`isRowSelected`, `getIsAll*`, `getIsSome*`, `getSelectedRowIds`, `selectRowsFn`)
1734+
**Category:** `memoization`, `micro`
17301735

1731-
**Risk:** None.
1736+
```ts
1737+
table_getIsAllRowsSelected: {
1738+
fn: () => table_getIsAllRowsSelected(table),
1739+
memoDeps: () => [
1740+
table.atoms.rowSelection?.get(),
1741+
table.getFilteredRowModel(),
1742+
table.options.enableRowSelection,
1743+
],
1744+
},
1745+
// getIsAllPageRowsSelected / getIsSomePageRowsSelected: same shape with getPaginatedRowModel()
1746+
table_getIsSomeRowsSelected: {
1747+
fn: () => table_getIsSomeRowsSelected(table),
1748+
memoDeps: () => [table.atoms.rowSelection?.get()], // delegates to memoized getSelectedRowIds
1749+
},
1750+
```
1751+
1752+
**Big-O:** O(filtered-rows) per call → O(1) until selection or the row model changes. Within the (now memo-gated) first scan, per-row atom reads drop from N to 1.
1753+
1754+
**Scale impact** (`getIsAllRowsSelected` `.some()` scans saved during scroll/render churn — dimension: renders × filtered rows, selection unchanged):
1755+
1756+
| Renders × Rows | Scan walks before | After (steady state) | Saved |
1757+
| --------------- | ----------------- | -------------------- | ---------- |
1758+
| 10 × 1,000 | 10,000 | 0 | 10,000 |
1759+
| 100 × 10,000 | 1,000,000 | 0 | 1,000,000 |
1760+
| 60 × 100,000 | 6,000,000 | 0 | 6,000,000 |
1761+
| 60 × 500,000 | 30,000,000 | 0 | 30,000,000 |
1762+
1763+
**Risk:** Low. Memo deps capture every input the getters read (selection, row model, `enableRowSelection`). The `getIsSome*` semantic change is intentional and documented.
17321764

17331765
---
17341766

0 commit comments

Comments
 (0)