perf: a file view re-reads the file only when a structural change really dropped it

Three tightenings, all measured with the reloads now reported on stderr.

`set_math_channels()` treats an identical list as no change at all. It rebuilt
unconditionally, and a rebuild replaces the buffer and drops its history -- which a file
view then has to re-read from disk. The comparison is the graphs', and it leaves the nodes'
`x`/`y` out deliberately: those are the canvas's own, no op reads them, and a graph that
merely *moved* should not cost the buffer.

`reload_file_view()` fires only when the store is actually empty. A change that really
happened leaves it so in a file view (the rebuild replaces it, and there is no producer to
refill it); one that found nothing to do leaves it full, and reading a 200 MB file again for
that is the waste. `ScopeModel::stored_slots()` is the one-line accessor it needed.

Every reload now says so, because a second read of a file is worth accounting for:

    [udpscope] reloaded <path> after a structural change: 402 samples back

Measured, `--open` a file:

    session with no graphs            0 reloads
    the math probe (3 real changes)   3 reloads, and all the readings stay equal
                                      (`math x2 402 samples vs 402 time slots`)

I had offered this as "one extra read at `--open` startup", and that was wrong: with no
session there is no `apply_math_channels()` call at all, so there was never an extra read
to remove. What the tightenings buy is that a no-op application is now free, and that a
reload cannot happen unless the buffer was really lost. Each *genuine* change still costs
one re-read -- a rebuild drops the history by design, and preserving it across a rebuild is
a model change rather than this one.
This commit is contained in:
Martino Ferrari committed 2026-10-12 19:55:31 +02:00
1 parent b090cc2a21
commit 47d49e75ff
5 files changed
+45 -1

No files matched your search

+10
View File
@@ -257,6 +257,11 @@ void ScopeModel::append_math_signals_locked() {
void ScopeModel::set_math_channels(std::vector<MathChannel> channels) {
std::lock_guard<std::mutex> lock(mutex_);
// An identical list is not a change. The rebuild below replaces the buffer and drops its history --
// which a file view then has to re-read from disk -- and `apply_math_channels()` runs this on every
// session start, where a session with no graphs hands over an empty list against a model that has
// none either. Comparing the graphs is what makes that call free.
if (channels == math_) return;
math_ = std::move(channels);
// Lift the previous synthetic entries out of the signal list; `append_math_signals_locked()`
// puts the current ones back, so an edit is the same kind of structural rebuild as a CONFIG.
@@ -314,6 +319,11 @@ void ScopeModel::push_loaded_slot(const std::vector<f32>& values, f64 t, f64 abs
push_math_sample(slot);
}
std::uint64_t ScopeModel::stored_slots() {
std::lock_guard<std::mutex> lock(mutex_);
return store_ ? store_->time().size() : 0;
}
void ScopeModel::push_math_sample(const std::vector<f32>& sample) {
// The common case is no derived channels at all, and this runs once per *stored slot* -- so the
// early-out is what keeps a stream with no math from paying for the feature.