improved perfs and event management
This commit is contained in:
1 parent
667ed0c100
commit
bb99be5889
17 files changed
+626
-59
No files matched your search
@@ -12,6 +12,33 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
`plan_partition()` rather than recomputing the factor, because two expressions for that
|
||||
decision is how the buffer and the view end up disagreeing about the stored rate. It
|
||||
takes `mutex()` itself — do not call it holding the lock.
|
||||
- **A rebuild that would plan the *same* storage clears the store instead of allocating one.**
|
||||
`rebuild_locked()` compares the new `Partition` against the current one with
|
||||
`Partition::same_storage_as()` — every field that decides an allocation: `time_capacity`,
|
||||
`time_stride`, `abs_time`, `base_dt` and the per-channel `stride`/`value_capacity` vectors — and
|
||||
on a match calls `BufferStore::reset()` rather than constructing a new one. `base_dt` is in the
|
||||
comparison on purpose: it is not a size, but it is what the slots *mean*, and keeping samples
|
||||
under a changed `base_dt` would re-date the whole ring.
|
||||
**Construction is the cost.** A `RingBuffer<T>` holds a `std::vector<std::atomic<T>>`, which is
|
||||
value-initialised, so making a store touches every byte of `--buffer-mb` — about a second per
|
||||
gigabyte, mostly first-touch page faults — while `reset()` is one relaxed store per ring plus an
|
||||
`invalidate()` and an epoch back to zero. It must leave nothing of the old history behind, which
|
||||
is why it clears the time ring, the absolute-time ring, every value ring, and both the published
|
||||
window and the pyramid of every channel: a reused store that kept a stale published window would
|
||||
hand the renderer samples the new plan does not describe.
|
||||
`reset()` runs on the consumer thread under `ScopeModel::mutex()`, which is what makes a
|
||||
`RingBuffer::clear()` safe here at all — the producer holds that same mutex for the whole of
|
||||
`on_data()`.
|
||||
Every allocation that *is* made says so: `[udpscope] buffer allocated: N channels, S s span,
|
||||
T ms` on stderr. A start used to emit fifteen of those; two is a real start, and a count that
|
||||
climbs again is this rule having stopped applying.
|
||||
- **Enables are applied in one batch, because each one re-plans.** `set_signals_enabled()` takes
|
||||
the mutex once, moves every signal in the list, and re-plans and bumps the generation **once** —
|
||||
and not at all when nothing actually moved, which is the common case for a restored session
|
||||
re-enabling what is already on. Calling `set_signal_enabled()` in a loop is a buffer rebuild per
|
||||
signal; `apply_layout()` and the waveform loader collect their wanted signals into one vector for
|
||||
that reason, and `apply_layout()` records the session's span **first**, with no channels bound, so
|
||||
the `apply_time_window()` that follows re-plans nothing.
|
||||
- **The shared clock is decimated to fit the requested window; channels never are.** A
|
||||
window too long to store at the wire rate used to be refused (the control was capped to
|
||||
`Partition::span_s`), which made a 600 s view of a 1 MS/s stream unreachable.
|
||||
|
||||
Reference in new issue
Block a user