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."""
19
from __future__ import absolute_import
28
class _LRUNode(object):
29
"""This maintains the linked-list which is the lru internals."""
31
__slots__ = ('prev', 'next_key', 'key', 'value')
33
def __init__(self, key, value):
35
self.next_key = _null_key
43
prev_key = self.prev.key
44
return '%s(%r n:%r p:%r)' % (self.__class__.__name__, self.key,
45
self.next_key, prev_key)
19
from collections import deque
21
from bzrlib import symbol_versioning
48
24
class LRUCache(object):
49
25
"""A class which manages a cache of entries, removing unused ones."""
51
def __init__(self, max_cache=100, after_cleanup_count=None):
27
def __init__(self, max_cache=100, after_cleanup_count=None,
28
after_cleanup_size=symbol_versioning.DEPRECATED_PARAMETER):
29
if symbol_versioning.deprecated_passed(after_cleanup_size):
30
symbol_versioning.warn('LRUCache.__init__(after_cleanup_size) was'
31
' deprecated in 1.11. Use'
32
' after_cleanup_count instead.',
34
after_cleanup_count = after_cleanup_size
53
# The "HEAD" of the lru linked list
54
self._most_recently_used = None
55
# The "TAIL" of the lru linked list
56
self._least_recently_used = None
37
self._queue = deque() # Track when things are accessed
38
self._refcount = {} # number of entries in self._queue for each key
57
39
self._update_max_cache(max_cache, after_cleanup_count)
59
41
def __contains__(self, key):
60
42
return key in self._cache
62
44
def __getitem__(self, key):
65
# Inlined from _record_access to decrease the overhead of __getitem__
66
# We also have more knowledge about structure if __getitem__ is
67
# succeeding, then we know that self._most_recently_used must not be
69
mru = self._most_recently_used
71
# Nothing to do, this node is already at the head of the queue
73
# Remove this node from the old location
75
next_key = node.next_key
76
# benchmarking shows that the lookup of _null_key in globals is faster
77
# than the attribute lookup for (node is self._least_recently_used)
78
if next_key is _null_key:
79
# 'node' is the _least_recently_used, because it doesn't have a
80
# 'next' item. So move the current lru to the previous node.
81
self._least_recently_used = node_prev
83
node_next = cache[next_key]
84
node_next.prev = node_prev
85
node_prev.next_key = next_key
86
# Insert this node at the front of the list
87
node.next_key = mru.key
89
self._most_recently_used = node
45
val = self._cache[key]
46
self._record_access(key)
94
50
return len(self._cache)
96
@symbol_versioning.deprecated_method(
97
symbol_versioning.deprecated_in((2, 5, 0)))
98
52
def add(self, key, value, cleanup=None):
53
"""Add a new value to the cache.
55
Also, if the entry is ever removed from the queue, call cleanup.
56
Passing it the key and value being removed.
58
:param key: The key to store it under
59
:param value: The object to store
60
:param cleanup: None or a function taking (key, value) to indicate
61
'value' sohuld be cleaned up.
63
if key in self._cache:
65
self._cache[key] = value
99
66
if cleanup is not None:
100
raise ValueError("Per-node cleanup functions no longer supported")
101
return self.__setitem__(key, value)
103
def __setitem__(self, key, value):
104
"""Add a new value to the cache"""
106
raise ValueError('cannot use _null_key as a key')
107
if key in self._cache:
108
node = self._cache[key]
110
self._record_access(node)
112
node = _LRUNode(key, value)
113
self._cache[key] = node
114
self._record_access(node)
67
self._cleanup[key] = cleanup
68
self._record_access(key)
116
70
if len(self._cache) > self._max_cache:
117
71
# Trigger the cleanup
120
def cache_size(self):
121
"""Get the number of entries we will cache."""
122
return self._max_cache
124
74
def get(self, key, default=None):
125
node = self._cache.get(key, None)
128
self._record_access(node)
75
if key in self._cache:
132
80
"""Get the list of keys currently cached.
155
96
# Make sure the cache is shrunk to the correct size
156
97
while len(self._cache) > self._after_cleanup_count:
157
98
self._remove_lru()
159
def _record_access(self, node):
99
# No need to compact the queue at this point, because the code that
100
# calls this would have already triggered it based on queue length
102
def __setitem__(self, key, value):
103
"""Add a value to the cache, there will be no cleanup function."""
104
self.add(key, value, cleanup=None)
106
def _record_access(self, key):
160
107
"""Record that key was accessed."""
161
# Move 'node' to the front of the queue
162
if self._most_recently_used is None:
163
self._most_recently_used = node
164
self._least_recently_used = node
166
elif node is self._most_recently_used:
167
# Nothing to do, this node is already at the head of the queue
169
# We've taken care of the tail pointer, remove the node, and insert it
172
if node is self._least_recently_used:
173
self._least_recently_used = node.prev
174
if node.prev is not None:
175
node.prev.next_key = node.next_key
176
if node.next_key is not _null_key:
177
node_next = self._cache[node.next_key]
178
node_next.prev = node.prev
180
node.next_key = self._most_recently_used.key
181
self._most_recently_used.prev = node
182
self._most_recently_used = node
185
def _remove_node(self, node):
186
if node is self._least_recently_used:
187
self._least_recently_used = node.prev
188
self._cache.pop(node.key)
189
# If we have removed all entries, remove the head pointer as well
190
if self._least_recently_used is None:
191
self._most_recently_used = None
192
if node.prev is not None:
193
node.prev.next_key = node.next_key
194
if node.next_key is not _null_key:
195
node_next = self._cache[node.next_key]
196
node_next.prev = node.prev
197
# And remove this node's pointers
199
node.next_key = _null_key
108
self._queue.append(key)
109
# Can't use setdefault because you can't += 1 the result
110
self._refcount[key] = self._refcount.get(key, 0) + 1
112
# If our access queue is too large, clean it up too
113
if len(self._queue) > self._compact_queue_length:
114
self._compact_queue()
116
def _compact_queue(self):
117
"""Compact the queue, leaving things in sorted last appended order."""
119
for item in self._queue:
120
if self._refcount[item] == 1:
121
new_queue.append(item)
123
self._refcount[item] -= 1
124
self._queue = new_queue
125
# All entries should be of the same size. There should be one entry in
126
# queue for each entry in cache, and all refcounts should == 1
127
if not (len(self._queue) == len(self._cache) ==
128
len(self._refcount) == sum(self._refcount.itervalues())):
129
raise AssertionError()
131
def _remove(self, key):
132
"""Remove an entry, making sure to maintain the invariants."""
133
cleanup = self._cleanup.pop(key, None)
134
val = self._cache.pop(key)
135
if cleanup is not None:
201
139
def _remove_lru(self):
202
140
"""Remove one entry from the lru, and handle consequences.
256
200
self._compute_size = compute_size
257
201
if compute_size is None:
258
202
self._compute_size = len
203
# This approximates that texts are > 0.5k in size. It only really
204
# effects when we clean up the queue, so we don't want it to be too
259
206
self._update_max_size(max_size, after_cleanup_size=after_cleanup_size)
260
207
LRUCache.__init__(self, max_cache=max(int(max_size/512), 1))
262
def __setitem__(self, key, value):
263
"""Add a new value to the cache"""
265
raise ValueError('cannot use _null_key as a key')
266
node = self._cache.get(key, None)
209
def add(self, key, value, cleanup=None):
210
"""Add a new value to the cache.
212
Also, if the entry is ever removed from the queue, call cleanup.
213
Passing it the key and value being removed.
215
:param key: The key to store it under
216
:param value: The object to store
217
:param cleanup: None or a function taking (key, value) to indicate
218
'value' sohuld be cleaned up.
220
if key in self._cache:
267
222
value_len = self._compute_size(value)
268
223
if value_len >= self._after_cleanup_size:
269
# The new value is 'too big to fit', as it would fill up/overflow
270
# the cache all by itself
271
trace.mutter('Adding the key %r to an LRUSizeCache failed.'
272
' value %d is too big to fit in a the cache'
273
' with size %d %d', key, value_len,
274
self._after_cleanup_size, self._max_size)
276
# We won't be replacing the old node, so just remove it
277
self._remove_node(node)
280
node = _LRUNode(key, value)
281
self._cache[key] = node
283
self._value_size -= self._compute_size(node.value)
284
225
self._value_size += value_len
285
self._record_access(node)
226
self._cache[key] = value
227
if cleanup is not None:
228
self._cleanup[key] = cleanup
229
self._record_access(key)
287
231
if self._value_size > self._max_size:
288
232
# Time to cleanup