Skip to content

Commit 79aa8c7

Browse files
committed
Fix #356: Improve RRCache performance.
1 parent bb6d71a commit 79aa8c7

File tree

2 files changed

+55
-1
lines changed

2 files changed

+55
-1
lines changed

src/cachetools/__init__.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,16 +288,33 @@ class RRCache(Cache):
288288
def __init__(self, maxsize, choice=random.choice, getsizeof=None):
289289
Cache.__init__(self, maxsize, getsizeof)
290290
self.__choice = choice
291+
self.__index = {}
292+
self.__keys = []
291293

292294
@property
293295
def choice(self):
294296
"""The `choice` function used by the cache."""
295297
return self.__choice
296298

299+
def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
300+
cache_setitem(self, key, value)
301+
if key not in self.__index:
302+
self.__index[key] = len(self.__keys)
303+
self.__keys.append(key)
304+
305+
def __delitem__(self, key, cache_delitem=Cache.__delitem__):
306+
cache_delitem(self, key)
307+
index = self.__index.pop(key)
308+
if index != len(self.__keys) - 1:
309+
last = self.__keys[-1]
310+
self.__keys[index] = last
311+
self.__index[last] = index
312+
self.__keys.pop()
313+
297314
def popitem(self):
298315
"""Remove and return a random `(key, value)` pair."""
299316
try:
300-
key = self.__choice(list(self))
317+
key = self.__choice(self.__keys)
301318
except IndexError:
302319
raise KeyError("%s is empty" % type(self).__name__) from None
303320
else:

tests/test_rr.py

100644100755
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import random
12
import unittest
23

34
from cachetools import RRCache
@@ -32,3 +33,39 @@ def test_rr(self):
3233
self.assertEqual(3, cache[3])
3334
self.assertEqual(4, cache[4])
3435
self.assertNotIn(0, cache)
36+
37+
def test_rr_getsizeof(self):
38+
cache = RRCache(maxsize=3, choice=min, getsizeof=lambda x: x)
39+
40+
cache[1] = 1
41+
cache[2] = 2
42+
43+
self.assertEqual(len(cache), 2)
44+
self.assertEqual(cache[1], 1)
45+
self.assertEqual(cache[2], 2)
46+
47+
cache[3] = 3
48+
49+
self.assertEqual(len(cache), 1)
50+
self.assertEqual(cache[3], 3)
51+
self.assertNotIn(1, cache)
52+
self.assertNotIn(2, cache)
53+
54+
with self.assertRaises(ValueError):
55+
cache[4] = 4
56+
self.assertEqual(len(cache), 1)
57+
self.assertEqual(cache[3], 3)
58+
59+
def test_rr_bad_choice(self):
60+
def bad_choice(seq):
61+
raise ValueError("test error")
62+
63+
cache = RRCache(maxsize=2, choice=bad_choice)
64+
cache[1] = 1
65+
cache[2] = 2
66+
with self.assertRaises(ValueError):
67+
cache[3] = 3
68+
69+
def test_rr_default_choice(self):
70+
cache = RRCache(maxsize=2)
71+
self.assertIs(cache.choice, random.choice)

0 commit comments

Comments
 (0)