You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
perf(flowcontrol): index active queues so dispatch scans O(active) flows
Every dispatch cycle, the fairness policy's Pick calls IterateQueues,
which snapshotted every registered queue in the band (empty ones
included) into a freshly allocated slice under the registry read lock.
Per-dispatch cost was O(registered flows) in CPU and allocation: 94-98%
of all allocation in the flow-control benchmarks, and superlinear
throughput decay under flow churn as registered flows accumulated
between GC cycles.
Each priority band now maintains a lock-free index of its non-empty
queues, updated on empty<->non-empty transitions detected inside the
queue's existing stats critical section (transitions are serialized per
queue by the queue mutex; the index is a sync.Map, so the update never
touches the registry mutex). IterateQueues ranges over the index
directly: no registry lock, no allocation, cost proportional to flows
that actually hold items.
This narrows IterateQueues to active (non-empty) queues, matching its
documented contract ("each active Flow"). Both production callers are
compatible: globalstrict.Pick already skips empty queues, and the
processor's cleanup/drain sweeps are no-ops on them. The view is
eventually consistent; callbacks must tolerate a visited queue reporting
Len() == 0, which they already did under the snapshot implementation.
Benchmarks (M4 Pro, -benchtime=3s unless noted):
- PerformanceMatrix L=1/P=1/F=5000/W=5000: 30.7k -> 277k d/s (9x),
42.2KB -> 1.8KB per op
- TopologyChurn: 16.7k -> 260k d/s (15x), 83KB -> 1.8KB per op
- FullPath (registry churn): 2.3k -> 118k d/s (52x); per-op cost now
flat with run length (was superlinear: 106us at 24k flows minted ->
439us at 73k)
Signed-off-by: Luke Van Drie <lukevandrie@google.com>
0 commit comments