From d840ce9f048b6d222ce35766087c0a44608bd6ed Mon Sep 17 00:00:00 2001 From: LeanBitLab <245915690+LeanBitLab@users.noreply.github.com> Date: Mon, 5 Oct 2026 15:00:26 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Fix=20O(N)=20allocation=20c?= =?UTF-8?q?hurn=20in=20AdaptiveCacheEngine=20eviction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Optimized the `trimToCapacity()` cache eviction loop in `AdaptiveCacheEngine.kt` by replacing the eager `Map.filter { ... }.minByOrNull { ... }` chain with a zero-allocation `for` loop. The previous implementation was allocating a completely new `LinkedHashMap` and wrapper sequence on *every single* item eviction, causing severe O(N) memory churn and garbage collection pressure when the cache had to discard many items at once. --- .../ui/main/components/MinimalButton.kt | 1 - .../reddittube/util/AdaptiveCacheEngine.kt | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/example/reddittube/ui/main/components/MinimalButton.kt b/app/src/main/java/com/example/reddittube/ui/main/components/MinimalButton.kt index 056265a..8879150 100644 --- a/app/src/main/java/com/example/reddittube/ui/main/components/MinimalButton.kt +++ b/app/src/main/java/com/example/reddittube/ui/main/components/MinimalButton.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp diff --git a/app/src/main/java/com/example/reddittube/util/AdaptiveCacheEngine.kt b/app/src/main/java/com/example/reddittube/util/AdaptiveCacheEngine.kt index 177860c..1f9df17 100644 --- a/app/src/main/java/com/example/reddittube/util/AdaptiveCacheEngine.kt +++ b/app/src/main/java/com/example/reddittube/util/AdaptiveCacheEngine.kt @@ -97,10 +97,25 @@ class AdaptiveCacheEngine( val target = dynamicCapacity val now = SystemClock.elapsedRealtime() while (store.size > target) { - val candidate = store.filter { (k, n) -> - !isProtectedKey(k) || (now - n.lastAccessedMs > protectedTtlMs) - }.minByOrNull { computeScore(it.value, now) } ?: break - val removedNode = store.remove(candidate.key) + // Optimization: Replace eager map.filter and minByOrNull with an allocation-free loop. + // Expected Impact: Eliminates temporary Map allocations per eviction, significantly reducing + // GC overhead and O(N) memory allocation churn during cache eviction spikes. + var candidateKey: K? = null + var minScore = Double.MAX_VALUE + + for ((k, n) in store) { + if (!isProtectedKey(k) || (now - n.lastAccessedMs > protectedTtlMs)) { + val score = computeScore(n, now) + if (score < minScore) { + minScore = score + candidateKey = k + } + } + } + + if (candidateKey == null) break + + val removedNode = store.remove(candidateKey) if (removedNode != null) { currentSizeBytes.addAndGet(-removedNode.sizeBytes) }