13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""A simple least-recently-used (LRU) cache."""
25
class _LRUNode(object):
26
"""This maintains the linked-list which is the lru internals."""
28
__slots__ = ('prev', 'next', 'key', 'value', 'cleanup', 'size')
30
def __init__(self, key, value, cleanup=None):
35
self.cleanup = cleanup
36
# TODO: We could compute this 'on-the-fly' like we used to, and remove
37
# one pointer from this object, we just need to decide if it
38
# actually costs us much of anything in normal usage
50
return '%s(%r n:%r p:%r)' % (self.__class__.__name__, self.key,
53
def run_cleanup(self):
54
if self.cleanup is not None:
55
self.cleanup(self.key, self.value)
57
# Just make sure to break any refcycles, etc
19
from collections import deque
61
23
class LRUCache(object):
62
24
"""A class which manages a cache of entries, removing unused ones."""
64
def __init__(self, max_cache=100, after_cleanup_count=None,
65
after_cleanup_size=symbol_versioning.DEPRECATED_PARAMETER):
66
if symbol_versioning.deprecated_passed(after_cleanup_size):
67
symbol_versioning.warn('LRUCache.__init__(after_cleanup_size) was'
68
' deprecated in 1.11. Use'
69
' after_cleanup_count instead.',
71
after_cleanup_count = after_cleanup_size
26
def __init__(self, max_cache=100, after_cleanup_size=None):
27
self._max_cache = max_cache
28
if after_cleanup_size is None:
29
self._after_cleanup_size = self._max_cache
31
self._after_cleanup_size = min(after_cleanup_size, self._max_cache)
33
self._compact_queue_length = 4*self._max_cache
73
# The "HEAD" of the lru linked list
74
self._most_recently_used = None
75
# The "TAIL" of the lru linked list
76
self._last_recently_used = None
77
self._update_max_cache(max_cache, after_cleanup_count)
37
self._queue = deque() # Track when things are accessed
38
self._refcount = {} # number of entries in self._queue for each key
79
40
def __contains__(self, key):
80
41
return key in self._cache
82
43
def __getitem__(self, key):
83
node = self._cache[key]
84
# Inlined from _record_access to decrease the overhead of __getitem__
85
# We also have more knowledge about structure if __getitem__ is
86
# succeeding, then we know that self._most_recently_used must not be
88
mru = self._most_recently_used
90
# Nothing to do, this node is already at the head of the queue
92
elif node is self._last_recently_used:
93
self._last_recently_used = node.prev
94
# Remove this node from the old location
97
if node_prev is not None:
98
node_prev.next = node_next
99
if node_next is not None:
100
node_next.prev = node_prev
101
# Insert this node to the front of the list
104
self._most_recently_used = node
44
val = self._cache[key]
45
self._record_access(key)
108
48
def __len__(self):
109
49
return len(self._cache)
112
"""Walk the LRU list, only meant to be used in tests."""
113
node = self._most_recently_used
115
if node.prev is not None:
116
raise AssertionError('the _most_recently_used entry is not'
117
' supposed to have a previous entry'
119
while node is not None:
120
if node.next is None:
121
if node is not self._last_recently_used:
122
raise AssertionError('only the last node should have'
123
' no next value: %s' % (node,))
125
if node.next.prev is not node:
126
raise AssertionError('inconsistency found, node.next.prev'
127
' != node: %s' % (node,))
128
if node.prev is None:
129
if node is not self._most_recently_used:
130
raise AssertionError('only the _most_recently_used should'
131
' not have a previous node: %s'
134
if node.prev.next is not node:
135
raise AssertionError('inconsistency found, node.prev.next'
136
' != node: %s' % (node,))
140
51
def add(self, key, value, cleanup=None):
141
52
"""Add a new value to the cache.
143
Also, if the entry is ever removed from the cache, call
54
Also, if the entry is ever removed from the queue, call cleanup.
55
Passing it the key and value being removed.
146
57
:param key: The key to store it under
147
58
:param value: The object to store
148
59
:param cleanup: None or a function taking (key, value) to indicate
149
'value' should be cleaned up.
60
'value' sohuld be cleaned up.
151
62
if key in self._cache:
152
node = self._cache[key]
155
node.cleanup = cleanup
157
node = _LRUNode(key, value, cleanup=cleanup)
158
self._cache[key] = node
159
self._record_access(node)
64
self._cache[key] = value
65
self._cleanup[key] = cleanup
66
self._record_access(key)
161
68
if len(self._cache) > self._max_cache:
162
69
# Trigger the cleanup
165
def cache_size(self):
166
"""Get the number of entries we will cache."""
167
return self._max_cache
169
72
def get(self, key, default=None):
170
node = self._cache.get(key, None)
173
self._record_access(node)
177
"""Get the list of keys currently cached.
179
Note that values returned here may not be available by the time you
180
request them later. This is simply meant as a peak into the current
183
:return: An unordered list of keys that are currently cached.
185
return self._cache.keys()
188
"""Get the key:value pairs as a dict."""
189
return dict((k, n.value) for k, n in self._cache.iteritems())
73
if key in self._cache:
191
77
def cleanup(self):
192
78
"""Clear the cache until it shrinks to the requested size.
194
80
This does not completely wipe the cache, just makes sure it is under
195
the after_cleanup_count.
81
the after_cleanup_size.
197
83
# Make sure the cache is shrunk to the correct size
198
while len(self._cache) > self._after_cleanup_count:
84
while len(self._cache) > self._after_cleanup_size:
199
85
self._remove_lru()
201
87
def __setitem__(self, key, value):
202
88
"""Add a value to the cache, there will be no cleanup function."""
203
89
self.add(key, value, cleanup=None)
205
def _record_access(self, node):
91
def _record_access(self, key):
206
92
"""Record that key was accessed."""
207
# Move 'node' to the front of the queue
208
if self._most_recently_used is None:
209
self._most_recently_used = node
210
self._last_recently_used = node
212
elif node is self._most_recently_used:
213
# Nothing to do, this node is already at the head of the queue
215
elif node is self._last_recently_used:
216
self._last_recently_used = node.prev
217
# We've taken care of the tail pointer, remove the node, and insert it
220
if node.prev is not None:
221
node.prev.next = node.next
222
if node.next is not None:
223
node.next.prev = node.prev
225
node.next = self._most_recently_used
226
self._most_recently_used = node
227
node.next.prev = node
230
def _remove_node(self, node):
231
if node is self._last_recently_used:
232
self._last_recently_used = node.prev
233
self._cache.pop(node.key)
234
# If we have removed all entries, remove the head pointer as well
235
if self._last_recently_used is None:
236
self._most_recently_used = None
93
self._queue.append(key)
94
# Can't use setdefault because you can't += 1 the result
95
self._refcount[key] = self._refcount.get(key, 0) + 1
97
# If our access queue is too large, clean it up too
98
if len(self._queue) > self._compact_queue_length:
101
def _compact_queue(self):
102
"""Compact the queue, leaving things in sorted last appended order."""
104
for item in self._queue:
105
if self._refcount[item] == 1:
106
new_queue.append(item)
108
self._refcount[item] -= 1
109
self._queue = new_queue
110
# All entries should be of the same size. There should be one entry in
111
# queue for each entry in cache, and all refcounts should == 1
112
if not (len(self._queue) == len(self._cache) ==
113
len(self._refcount) == sum(self._refcount.itervalues())):
114
raise AssertionError()
116
def _remove(self, key):
117
"""Remove an entry, making sure to maintain the invariants."""
118
cleanup = self._cleanup.pop(key)
119
val = self._cache.pop(key)
120
if cleanup is not None:
239
124
def _remove_lru(self):
240
125
"""Remove one entry from the lru, and handle consequences.
290
164
The function should take the form "compute_size(value) => integer".
291
165
If not supplied, it defaults to 'len()'
167
# This approximates that texts are > 0.5k in size. It only really
168
# effects when we clean up the queue, so we don't want it to be too
170
LRUCache.__init__(self, max_cache=int(max_size/512))
171
self._max_size = max_size
172
if after_cleanup_size is None:
173
self._after_cleanup_size = self._max_size
175
self._after_cleanup_size = min(after_cleanup_size, self._max_size)
293
177
self._value_size = 0
294
178
self._compute_size = compute_size
295
179
if compute_size is None:
296
180
self._compute_size = len
297
self._update_max_size(max_size, after_cleanup_size=after_cleanup_size)
298
LRUCache.__init__(self, max_cache=max(int(max_size/512), 1))
300
182
def add(self, key, value, cleanup=None):
301
183
"""Add a new value to the cache.
303
Also, if the entry is ever removed from the cache, call
185
Also, if the entry is ever removed from the queue, call cleanup.
186
Passing it the key and value being removed.
306
188
:param key: The key to store it under
307
189
:param value: The object to store
308
190
:param cleanup: None or a function taking (key, value) to indicate
309
'value' should be cleaned up.
191
'value' sohuld be cleaned up.
311
node = self._cache.get(key, None)
193
if key in self._cache:
312
195
value_len = self._compute_size(value)
313
196
if value_len >= self._after_cleanup_size:
314
# The new value is 'too big to fit', as it would fill up/overflow
315
# the cache all by itself
316
trace.mutter('Adding the key %r to an LRUSizeCache failed.'
317
' value %d is too big to fit in a the cache'
318
' with size %d %d', key, value_len,
319
self._after_cleanup_size, self._max_size)
321
# We won't be replacing the old node, so just remove it
322
self._remove_node(node)
323
if cleanup is not None:
327
node = _LRUNode(key, value, cleanup=cleanup)
328
self._cache[key] = node
330
self._value_size -= node.size
331
node.size = value_len
332
198
self._value_size += value_len
333
self._record_access(node)
199
self._cache[key] = value
200
self._cleanup[key] = cleanup
201
self._record_access(key)
335
203
if self._value_size > self._max_size:
336
204
# Time to cleanup