From ce00621a4a118ef53f24dcbd6023c96961a510db Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 13 Oct 2026 16:00:10 -0500 Subject: [PATCH] perf(market): precompute the address-picker selection flag once The portfolio address picker's sort comparator looked up each address in the selected-address set on every comparison, two hash lookups per call, and the per-row draw repeated the lookup again. On a long address list that is a lot of redundant hashing every frame. - Each filtered candidate now carries its selected flag, looked up once when the list is built. The comparator reads the stored flag, so the sort produces the same ordering with no lookups of its own. Co-Authored-By: Claude Opus 4.8 --- src/ui/windows/market_tab.cpp | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/ui/windows/market_tab.cpp b/src/ui/windows/market_tab.cpp index 9c90fb5..948bd0e 100644 --- a/src/ui/windows/market_tab.cpp +++ b/src/ui/windows/market_tab.cpp @@ -660,7 +660,13 @@ static void pfDrawAddressSection(App* app) } ImGui::Dummy(ImVec2(0, Layout::spacingXs())); std::string search = s_pfEdit.search; - std::vector filtered; + // Selected-address set, built once (membership test for the sort flag and the per-row check below). + std::unordered_set selSet(s_pfEdit.addrs.begin(), s_pfEdit.addrs.end()); + // Each candidate carries its "selected" flag, looked up ONCE (the old comparator did two hash lookups per + // comparison). Same keys + same comparison outcomes => std::sort yields the same permutation as before. + struct PfPick { const AddressInfo* a; bool sel; }; + std::vector filtered; + filtered.reserve(state.addresses.size()); for (const auto& a : state.addresses) { if (s_pfEdit.typeFilter == 1 && a.type != "shielded") continue; if (s_pfEdit.typeFilter == 2 && a.type != "transparent") continue; @@ -669,20 +675,15 @@ static void pfDrawAddressSection(App* app) std::string hay = a.address + " " + app->getAddressLabel(a.address); if (!util::containsIgnoreCase(hay, search)) continue; } - filtered.push_back(&a); + filtered.push_back({&a, selSet.count(a.address) != 0}); } - // Build the selected-address set once — the sort comparator and the per-row membership test below - // both consulted it via a linear PortfolioEntryContains scan (O(n) each) every frame. - std::unordered_set selSet(s_pfEdit.addrs.begin(), s_pfEdit.addrs.end()); - std::sort(filtered.begin(), filtered.end(), [&](const AddressInfo* x, const AddressInfo* y) { - bool sx = selSet.count(x->address) != 0; - bool sy = selSet.count(y->address) != 0; - if (sx != sy) return sx; - return x->balance > y->balance; + std::sort(filtered.begin(), filtered.end(), [](const PfPick& x, const PfPick& y) { + if (x.sel != y.sel) return x.sel; + return x.a->balance > y.a->balance; }); if (selAllClicked) - for (const AddressInfo* a : filtered) - if (data::PortfolioEntryAdd(s_pfEdit.addrs, a->address)) selSet.insert(a->address); + for (const PfPick& p : filtered) + if (data::PortfolioEntryAdd(s_pfEdit.addrs, p.a->address)) selSet.insert(p.a->address); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImDrawList* ldl = material::BeginFadeScrollChild("##pfAddrList", s_pfEdit.addrFade, ImVec2(Layout::spacingMd(), Layout::spacingSm()), dp); @@ -690,8 +691,8 @@ static void pfDrawAddressSection(App* app) float lw = ImGui::GetContentRegionAvail().x; if (filtered.empty()) Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("portfolio_no_addr_match")); - for (const AddressInfo* ap : filtered) { - const AddressInfo& a = *ap; + for (const PfPick& pk : filtered) { + const AddressInfo& a = *pk.a; bool inSet = selSet.count(a.address) != 0; ImVec2 rmn = ImGui::GetCursorScreenPos(); ImVec2 rmx(rmn.x + lw, rmn.y + rowH);