Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
## 2024-05-24 - Started

## 2024-05-24 - O(N) Iteration in Cache Size Calculation
**Learning:** Calculating dynamic capacity dynamically via `store.values.sumOf { it.sizeBytes }` effectively incurs an O(N) cost on every `put` because it runs when `trimToCapacity()` calls `dynamicCapacity`. When cache sizes get large, this causes O(N²) overall insertion performance and UI stutters.
**Action:** Use an `AtomicLong` (O(1)) to track the running sum of cache size instead of dynamically iterating through the entire collection.
## 2024-06-25 - Avoid Eager Collection Operations in Tight Loops
**Learning:** Using `store.filter { ... }.minByOrNull { ... }` inside a `while` loop for cache eviction creates temporary `LinkedHashMap` instances on every iteration, leading to excessive GC pressure and O(N) memory allocations per loop cycle.
**Action:** Replace functional pipelines that allocate intermediate collections with explicit, allocation-free `for` loops when iterating over Maps or Collections within performance-critical tight loops (like cache eviction).
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,24 @@ class AdaptiveCacheEngine<K : Any, V : Any>(
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
// ⚡ Bolt: Removed eager store.filter Map creation to prevent heavy GC pressure.
// Using an explicit loop avoids allocating temporary collections on every eviction iteration.
var minEntry: Map.Entry<K, CacheNode<V>>? = null
var minScore = Double.MAX_VALUE

for (entry in store.entries) {
val k = entry.key
val n = entry.value
if (!isProtectedKey(k) || (now - n.lastAccessedMs > protectedTtlMs)) {
val score = computeScore(n, now)
if (score < minScore) {
minScore = score
minEntry = entry
}
}
}

val candidate = minEntry ?: break
val removedNode = store.remove(candidate.key)
if (removedNode != null) {
currentSizeBytes.addAndGet(-removedNode.sizeBytes)
Expand Down
Loading