perf: round 16 — shares-lane & contextMap incremental builders, folder/href/disposition/preview alloc cuts

Finishes the route-level half of the O(N²/page) grouped-listing class ROUND15
fixed inside ResourceList, plus a backend CPU/alloc micro-pack. Every change is
benchmark-gated with a hard rollback rule; no PostgreSQL needed for any arm
(benches/ROUND16.md).

Frontend (vitest):
- F1 "My shares" lanes: the `lanes` $derived.by re-bucketed the whole
  accumulated grant list on every page and every grant edit. SharedLanesBuilder
  re-emits only the fresh page (fan-out + first-appearance header), reusing
  untouched lanes' array refs. 25.5x fewer emit calls, 8.8x wall, O(N²/page)→O(N).
- F2 contextMap (trash/recent/favorites/shared-with-me): each rebuilt a fresh
  N-key Map, re-hashing every accumulated id, per page. primeContextPage holds a
  persistent SvelteMap primed per page (the shipped favoriteIds shape).
  25.5x fewer entry calls, 7.2x wall.
- Extracted the shared O(1) append test (isAppendExtension); F1's gate re-covers it.

Backend (counting-allocator):
- M1 folder display constants Arc::from -> intern_display (3 sites): 3 -> 0 allocs/row.
- M2 build_content_disposition (every download + Range seek): 3 -> 1 alloc, 6x, 2.67x wall.
- M3 nc_href (every NC PROPFIND/REPORT href): Vec+join+format -> one pre-sized
  buffer, keeping urlencoding::encode (byte-identical). 38 -> 27 allocs/op.
- M4 NC preview fileId: collect-then-parse -> borrow-slice parse. 4 -> 0 allocs.

Gates: sharedLanes/listContext.bench.test.ts, examples/bench_round16_micro.rs
(GATE PASS all sections). Frontend: vitest 331 pass, svelte-check clean.
Backend: clippy -D warnings clean, 524 lib tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193NjactJVqfU32gxeJDj8m
This commit is contained in:
Claude
2026-07-19 17:30:22 +00:00
parent 8537b5949a
commit 955f4a7b9f
20 changed files with 1388 additions and 123 deletions
@@ -0,0 +1,141 @@
import { describe, expect, it } from 'vitest';
import { primeContextPage } from './listContext';
/**
* Benchmark gate for the incremental route-level `contextMap` maintenance
* (primeContextPage) that replaced `contextMap = $derived(new Map(raw.map(...)))`
* on the trash / recent / favorites / shared-with-me routes.
*
* Audit finding (ROUND16 §F2, the route-level half of the class ROUND15 §F1
* fixed inside ResourceList): each route paged its rows in via
* `raw = [...raw, ...page.items]` and rebuilt a brand-new Map — hashing every
* accumulated id — on EVERY page. O(N) per page ⇒ Σ O(N²/page) across a drain,
* plus a fresh Map instance each page. The fix holds one persistent map and
* sets only the fresh page's entries (mirrors the shipped `favoriteIds`
* SvelteSet, ROUND14 §F2).
*
* Gates (rollback rule: an AFTER that fails to beat its BEFORE fails CI):
* 1. Equivalence — at EVERY page, the incrementally-primed map is deep-equal
* to a full `new Map(cumulative.map(entry))` rebuild, including skipped
* entries (drives → null) and the reset path.
* 2. Perf — `entry` work collapses from Σ O(N²/page) to O(N) across the drain
* (deterministic call count) and wall drops ≥3x.
*/
interface Ctx {
date: string | null;
ownerId: string | null;
}
interface Raw {
resource: { id: string; updated_by: string | null };
resource_type: 'file' | 'folder' | 'drive';
accessed_at: string;
}
const pad = (i: number) => i.toString().padStart(6, '0');
/** Item `i`; every 10th is a `drive` (skipped by the shared-with-me-style entry). */
function raw(i: number): Raw {
return {
resource: { id: `res-${pad(i)}`, updated_by: `user-${i % 8}` },
resource_type: i % 10 === 0 ? 'drive' : i % 3 === 0 ? 'folder' : 'file',
accessed_at: `2026-07-${pad((i % 27) + 1).slice(-2)}`
};
}
/** Maps a raw item to its `[id, ctx]`, skipping drives (returns null) — counts calls. */
function makeEntry(counter?: { n: number }): (it: Raw) => readonly [string, Ctx] | null {
return (it) => {
if (counter) counter.n++;
if (it.resource_type === 'drive') return null;
return [it.resource.id, { date: it.accessed_at, ownerId: it.resource.updated_by }];
};
}
/** Verbatim BEFORE: the old derive — a fresh Map hashing the whole cumulative list. */
function rebuild(
cumulative: Raw[],
entry: (it: Raw) => readonly [string, Ctx] | null
): Map<string, Ctx> {
const m = new Map<string, Ctx>();
for (const it of cumulative) {
const e = entry(it);
if (e !== null) m.set(e[0], e[1]);
}
return m;
}
const PAGE = 50;
const PAGES = 50; // 2 500-item drain
describe('incremental contextMap (benchmark gate)', () => {
it('stays deep-equal to the full rebuild at every page (incl. skipped drives)', () => {
const all = Array.from({ length: PAGE * PAGES }, (_, i) => raw(i));
const entry = makeEntry();
const map = new Map<string, Ctx>();
for (let p = 1; p <= PAGES; p++) {
const page = all.slice((p - 1) * PAGE, p * PAGE);
primeContextPage(map, p === 1, page, entry);
const reference = rebuild(all.slice(0, p * PAGE), entry);
expect(new Map(map), `page ${p}`).toEqual(reference);
}
});
it('clears on reset and re-primes to the reset page only', () => {
const all = Array.from({ length: 200 }, (_, i) => raw(i));
const entry = makeEntry();
const map = new Map<string, Ctx>();
primeContextPage(map, true, all.slice(0, 100), entry);
primeContextPage(map, false, all.slice(100, 150), entry);
// Reset with a disjoint page: prior ids must be gone.
const resetPage = all.slice(150, 200);
primeContextPage(map, true, resetPage, entry);
expect(new Map(map)).toEqual(rebuild(resetPage, entry));
});
it('collapses entry work from Σ O(N²/page) to O(N) and runs ≥3x faster', () => {
const N = PAGE * PAGES;
const all = Array.from({ length: N }, (_, i) => raw(i));
// Deterministic call-count gate (the hard rollback gate): incremental
// computes each item's entry exactly once; the rebuild is quadratic. This
// holds regardless of machine load.
const afterCounter = { n: 0 };
const afterEntry = makeEntry(afterCounter);
const countMap = new Map<string, Ctx>();
for (let p = 1; p <= PAGES; p++) {
primeContextPage(countMap, p === 1, all.slice((p - 1) * PAGE, p * PAGE), afterEntry);
}
const beforeCounter = { n: 0 };
const beforeEntry = makeEntry(beforeCounter);
for (let p = 1; p <= PAGES; p++) rebuild(all.slice(0, p * PAGE), beforeEntry);
expect(afterCounter.n).toBe(N);
expect(beforeCounter.n).toBe((PAGES * (PAGES + 1) * PAGE) / 2);
expect(afterCounter.n).toBeLessThan(beforeCounter.n / 5);
// Wall gate — best-of-3 (min) per arm to shrug off scheduler / GC noise
// under a saturated test runner (mirrors round14 §F1's `Math.min` pattern).
const entry = makeEntry();
const runAfter = () => {
const m = new Map<string, Ctx>();
const t = performance.now();
for (let p = 1; p <= PAGES; p++) {
primeContextPage(m, p === 1, all.slice((p - 1) * PAGE, p * PAGE), entry);
}
return performance.now() - t;
};
const runBefore = () => {
const t = performance.now();
for (let p = 1; p <= PAGES; p++) rebuild(all.slice(0, p * PAGE), entry);
return performance.now() - t;
};
const afterMs = Math.min(runAfter(), runAfter(), runAfter());
const beforeMs = Math.min(runBefore(), runBefore(), runBefore());
console.info(
`contextMap ${PAGES}×${PAGE}: before ${beforeCounter.n} entry calls / ${beforeMs.toFixed(1)} ms — after ${afterCounter.n} calls / ${afterMs.toFixed(1)} ms (${(beforeCounter.n / afterCounter.n).toFixed(1)}x fewer calls, ${(beforeMs / afterMs).toFixed(1)}x wall)`
);
expect(afterMs).toBeLessThan(beforeMs / 3);
});
});