Closed RyanDevlin closed 3 years ago
I have identified the fix for this issue. Would love to contribute
Out of curiosity, what was the fix you identified?
@RyanDevlin Please find the diff for the fix below. Basically when there's only one entry in lfu item's map, increment removes that entry from it and create a new lfu item for that entry. That's why list keeps growing on every increment. Looking in the diff would give you a better explanation.
diff --git a/lfu.go b/lfu.go
index f781a1f..d42d5d2 100644
--- a/lfu.go
+++ b/lfu.go
@@ -181,17 +181,22 @@ func (c *LFUCache) increment(item *lfuItem) {
currentFreqElement := item.freqElement
currentFreqEntry := currentFreqElement.Value.(*freqEntry)
nextFreq := currentFreqEntry.freq + 1
- delete(currentFreqEntry.items, item)
-
- nextFreqElement := currentFreqElement.Next()
- if nextFreqElement == nil {
- nextFreqElement = c.freqList.InsertAfter(&freqEntry{
- freq: nextFreq,
- items: make(map[*lfuItem]struct{}),
- }, currentFreqElement)
+ if len(currentFreqEntry.items) == 1 {
+ currentFreqEntry.freq = nextFreq
+ } else {
+ delete(currentFreqEntry.items, item)
+
+ nextFreqElement := currentFreqElement.Next()
+ if nextFreqElement == nil {
+ nextFreqElement = c.freqList.InsertAfter(&freqEntry{
+ freq: nextFreq,
+ items: make(map[*lfuItem]struct{}),
+ }, currentFreqElement)
+ }
+ nextFreqElement.Value.(*freqEntry).items[item] = struct{}{}
+
+ item.freqElement = nextFreqElement
}
- nextFreqElement.Value.(*freqEntry).items[item] = struct{}{}
- item.freqElement = nextFreqElement
}
// evict removes the least frequence item from the cache.
Thanks @RyanDevlin @shubhati.
Definitely, keeping an empty freqEntry
is very inefficient in memory usage.
This problem occurs not only when it calls increment
, but also when we delete an element of freqEntry
that has only one element in remove
.
@shubhati, thanks for your patch and contribution! But it seems to not work if nextFreqElement.freq
does not match nextFreq
. I'll open a pull-request that includes tests and a patch that fixes remove
later.
Description
There is a fairly severe memory leak in the implementation of the LFU cache. This leak may also manifest itself in the other algorithms, although I haven't checked yet.
Reproducing
Here is what my main function looks like to produce this leak.
Evidence
As you can see in the function above. We first initialize a new LFU cache that can hold 10 items. We then set 4 of these items to key:value pairs. Finally we read the first item 400,000 times, and print it's value on the last run.
Below is the output of the memory profiler looking at the functions making the most memory allocations.
This immediately indicates that somehow, the 4 key value pairs placed into the cache, along with the 400,000 reads performed, required an allocation of 14MB of memory. As we can see, the majority of all the allocations came from inside the github.com/bluele/gcache.(*LFUCache).increment frunction.
If we take a look at this function we see the following:
At first glance, it becomes obvious that the frequency list inside the cache struct (c.freqList) is growing quite large. If we trace the increment function upwards we can see it is called every time we request an item from the cache, as long as we did not set a timeout on our items (in this case we did not).
The result of this is that c.freqList grows by one list entry every time we request an item from the cache. We can see this behavior in the Devel debugger:
The above steps were as follows. Set a breakpoint on line 186, in the middle of the increment function. Run continue to skip to the next time we hit the break point. Do this 39 more times to allow 40 consecutive reads from the cache. At this point, we print the frequency list in the cache with (dlv) print c.freqList. This shows us that the list is of length 40, even though there are only 4 items in the cache.
If we follow the debugger further this pattern continues and the list grows linearly. This is obviously unacceptable behavior and ends up consuming large amounts of memory for operations that should consume none.