perf: memoize per-frame render hot paths (console, transactions, recent lists)
Immediate-mode render functions re-run every frame; these rebuilt O(N) state each time even when nothing changed. From a 6-lens perf audit, each finding verified on a hot-path basis: - Console: ConsoleModel gains revision(); the full-model filter scan and the glyph-by-glyph text-layout pass (BuildConsoleLayout) rebuild only when the model / filter / wrap-width / zoom change — previously it re-shaped up to 10,000 lines every frame even when idle/scrolled. clear() force-invalidates the memo mid-render (no OOB on the just-emptied visible set). - Transactions: the summary-card totals memoize behind the tab's existing FNV-1a fingerprint (also folds away a now-duplicate O(N) display-key pass). - Send / Receive recent lists: early-exit the prefix scan (state.transactions is kept newest-first) instead of filtering the whole tx history every frame. - network_refresh_service: O(new x total) txid find-and-replace -> hash map. - Sidebar unconfirmed-tx badge cached on last_tx_update + tx count; the daemon-memory probe (/proc scan on Linux, popen on macOS) throttled to ~1.5s. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -820,11 +820,23 @@ void ConsoleTab::renderOutput()
|
||||
// segment records which bytes of the source text appear on that visual row, so
|
||||
// hit-testing and selection highlight can map screen positions to exact char offsets.
|
||||
float wrap_width = ClampConsoleWrapWidth(ImGui::GetContentRegionAvail().x, padX);
|
||||
ImFontConsoleMeasure measure(ImGui::GetFont(), ImGui::GetFontSize());
|
||||
layout_ = BuildConsoleLayout(
|
||||
static_cast<int>(visible_indices_.size()),
|
||||
[this](int vi) -> const std::string& { return model_[visible_indices_[vi]].text; },
|
||||
wrap_width, line_height, interLineGap, measure);
|
||||
// Memoize the text-shaping pass: rebuild only when the visible set, wrap width, line height, gap or
|
||||
// zoom changed. Otherwise this re-wrapped and re-measured every visible line (glyph-by-glyph) every
|
||||
// frame, even while idle/scrolled. layout_ is a member, so the cached geometry stays valid for
|
||||
// drawVisibleLines / screenToTextPos when we skip the rebuild.
|
||||
std::uint64_t layoutKey = vis_generation_ * 1000003ull;
|
||||
layoutKey = layoutKey * 131ull + static_cast<std::uint64_t>(wrap_width * 16.0f);
|
||||
layoutKey = layoutKey * 131ull + static_cast<std::uint64_t>(line_height * 16.0f);
|
||||
layoutKey = layoutKey * 131ull + static_cast<std::uint64_t>(interLineGap * 16.0f);
|
||||
layoutKey = layoutKey * 131ull + static_cast<std::uint64_t>(s_console_zoom * 1000.0f);
|
||||
if (layoutKey != layout_key_) {
|
||||
ImFontConsoleMeasure measure(ImGui::GetFont(), ImGui::GetFontSize());
|
||||
layout_ = BuildConsoleLayout(
|
||||
static_cast<int>(visible_indices_.size()),
|
||||
[this](int vi) -> const std::string& { return model_[visible_indices_[vi]].text; },
|
||||
wrap_width, line_height, interLineGap, measure);
|
||||
layout_key_ = layoutKey;
|
||||
}
|
||||
|
||||
// Mouse/keyboard interaction (wheel-up detach, selection drag, Ctrl+C/A). Raw IO bypasses
|
||||
// the child window's event consumption.
|
||||
@@ -891,6 +903,20 @@ void ConsoleTab::renderOutput()
|
||||
|
||||
void ConsoleTab::computeVisibleLines(bool& hasTextFilter, std::string& filterLower)
|
||||
{
|
||||
// Memoize: rebuild the visible set only when the model changed or the filter state changed.
|
||||
// Otherwise this scanned the entire (up to 10k-line) model with a per-line filter predicate every
|
||||
// frame. The out-params + filter_match_count_/folding_active_ are members that stay valid until the
|
||||
// key moves, so an early return leaves last frame's (still-correct) results in place.
|
||||
std::uint64_t visKey = model_.revision() * 1000003ull;
|
||||
for (const char* p = filter_text_; *p; ++p) visKey = visKey * 131ull + static_cast<unsigned char>(*p);
|
||||
visKey = visKey * 2ull + (s_daemon_messages_enabled ? 1u : 0u);
|
||||
visKey = visKey * 2ull + (s_errors_only_enabled ? 1u : 0u);
|
||||
visKey = visKey * 2ull + (s_rpc_trace_enabled ? 1u : 0u);
|
||||
visKey = visKey * 2ull + (s_app_messages_enabled ? 1u : 0u);
|
||||
if (visKey == vis_key_) return; // nothing that affects the visible set changed
|
||||
vis_key_ = visKey;
|
||||
++vis_generation_; // the layout pass keys off this
|
||||
|
||||
ConsoleOutputFilter outputFilter{filter_text_, s_daemon_messages_enabled,
|
||||
s_errors_only_enabled, s_rpc_trace_enabled,
|
||||
s_app_messages_enabled};
|
||||
@@ -2079,6 +2105,12 @@ void ConsoleTab::clear()
|
||||
// line indices) here to avoid an out-of-bounds crash. computeVisibleLines() rebuilds them next frame.
|
||||
visible_indices_.clear();
|
||||
selection_.clear();
|
||||
// Force both memoized passes to rebuild: renderOutput() runs later THIS frame against the now-empty
|
||||
// visible_indices_, and computeVisibleLines() recomputes next frame — without these resets the layout
|
||||
// memo would skip the rebuild and keep stale geometry for the just-emptied set.
|
||||
vis_key_ = ~0ull;
|
||||
layout_key_ = ~0ull;
|
||||
++vis_generation_;
|
||||
stop_confirm_pending_ = false; // a pending 'stop' confirmation is cancelled by clearing
|
||||
addLine(TR("console_cleared"), ConsoleChannel::Info);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user