From 740c7cf1bb2c738f50fd00146e7bcde9caa04242 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 22:13:53 +0200 Subject: [PATCH] perf(dbviewer): cache filteredHistory.Count once per export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DB export loop called filteredHistory.Count twice per 5000-message batch — once for the progress fraction, once for the status text. filteredHistory is built lazily by Filter() and re-enumerates on every .Count access, so on a 2M-message history each batch was paying for two full passes through the IEnumerable. Materializing the count once at the top reduces the export to a single O(N) traversal as intended. The RunOnTick + delayTicks pacing is intentional (keeps SeString encoding on the framework thread and rate-limits the export to avoid laggy frames during long exports), so the rest of the loop stays put. --- HellionChat/Ui/DbViewer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/HellionChat/Ui/DbViewer.cs b/HellionChat/Ui/DbViewer.cs index b4f1164..e766ca7 100644 --- a/HellionChat/Ui/DbViewer.cs +++ b/HellionChat/Ui/DbViewer.cs @@ -391,6 +391,10 @@ public class DbViewer : Window await rangeMessageEnumerator.DisposeAsync(); var filteredHistory = Filter(messageHistory); + // Materialize Count once — re-enumerating the IEnumerable on + // every batch (twice per batch in the Notification update) + // turned the export into an O(N²) hot loop on large histories. + var totalCount = filteredHistory.Count; var sb = new StringBuilder(); await using var stream = new StreamWriter(Path.Join(InputPath, $"Chat2_{DateTime.Now:yyyy_dd_M__HH_mm_ss}.txt")); @@ -416,8 +420,8 @@ public class DbViewer : Window } }, delayTicks: 5); - Notification.Progress = (float)batch / filteredHistory.Count; - Notification.Content = $"Exported {batch} of {filteredHistory.Count} messages"; + Notification.Progress = (float)batch / totalCount; + Notification.Content = $"Exported {batch} of {totalCount} messages"; await stream.WriteAsync(sb.ToString()); sb.Clear(); }