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
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
## 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-03 - Eager Collection Operations in Tight Loops
**Learning:** In Kotlin, using eager collection operations like `Map.filter` inside tight loops (like a `while` loop for cache eviction) allocates temporary collections on each iteration. This causes heavy garbage collection pressure, leading to UI stutters and poor performance.
**Action:** Avoid eager collection operations like `filter` or `map` in performance-critical code or tight loops. Prefer allocation-free explicit `for` loops for these scenarios to minimize temporary object allocations.
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,10 +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
val removedNode = store.remove(candidate.key)
// Optimization: Replaced store.filter().minByOrNull() with an explicit loop.
// This prevents the allocation of temporary maps inside a tight while-loop,
// significantly reducing garbage collection pressure during cache evictions.
// Expected impact: Removes O(N) map allocations per evicted item.
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)
}
Expand Down
Loading