Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## 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.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class AdaptiveCacheEngine<K : Any, V : Any>(

private val store = ConcurrentHashMap<K, CacheNode<V>>()
private val lambda = 0.005 // Time decay parameter
private val currentSizeBytes = java.util.concurrent.atomic.AtomicLong(0L)

/**
* Dynamically calculates target capacity C_max based on available JVM memory:
Expand All @@ -35,7 +36,7 @@ class AdaptiveCacheEngine<K : Any, V : Any>(
val runtime = Runtime.getRuntime()
val availableMemory = runtime.maxMemory() - (runtime.totalMemory() - runtime.freeMemory())
val allocatable = (availableMemory * memoryFraction).toLong()
val totalSize = store.values.sumOf { it.sizeBytes }
val totalSize = currentSizeBytes.get()
val count = store.size.coerceAtLeast(1)
val avgItemSize = (totalSize / count).coerceAtLeast(1024L)
val calcCapacity = (allocatable / avgItemSize).toInt()
Expand Down Expand Up @@ -70,7 +71,12 @@ class AdaptiveCacheEngine<K : Any, V : Any>(
fun put(key: K, value: V) {
val now = SystemClock.elapsedRealtime()
val weight = sizeEstimator(value)
store[key] = CacheNode(value = value, frequency = 1, lastAccessedMs = now, sizeBytes = weight)
val oldNode = store.put(key, CacheNode(value = value, frequency = 1, lastAccessedMs = now, sizeBytes = weight))
if (oldNode != null) {
currentSizeBytes.addAndGet(weight - oldNode.sizeBytes)
} else {
currentSizeBytes.addAndGet(weight)
}
trimToCapacity()
}

Expand All @@ -94,13 +100,19 @@ class AdaptiveCacheEngine<K : Any, V : Any>(
val candidate = store.filter { (k, n) ->
!isProtectedKey(k) || (now - n.lastAccessedMs > protectedTtlMs)
}.minByOrNull { computeScore(it.value, now) } ?: break
store.remove(candidate.key)
val removedNode = store.remove(candidate.key)
if (removedNode != null) {
currentSizeBytes.addAndGet(-removedNode.sizeBytes)
}
}
}

fun containsKey(key: K): Boolean = store.containsKey(key)

fun clear() = store.clear()
fun clear() {
store.clear()
currentSizeBytes.set(0L)
}

fun size(): Int = store.size
}
Loading