Show More
@@ -1904,6 +1904,11 b' def perflrucache(ui, size=4, gets=10000,' | |||
|
1904 | 1904 | for i in xrange(sets): |
|
1905 | 1905 | setseq.append(random.randint(0, sys.maxint)) |
|
1906 | 1906 | |
|
1907 | def doinserts(): | |
|
1908 | d = util.lrucachedict(size) | |
|
1909 | for v in setseq: | |
|
1910 | d.insert(v, v) | |
|
1911 | ||
|
1907 | 1912 | def dosets(): |
|
1908 | 1913 | d = util.lrucachedict(size) |
|
1909 | 1914 | for v in setseq: |
@@ -1935,6 +1940,7 b' def perflrucache(ui, size=4, gets=10000,' | |||
|
1935 | 1940 | benches = [ |
|
1936 | 1941 | (doinit, b'init'), |
|
1937 | 1942 | (dogets, b'gets'), |
|
1943 | (doinserts, b'inserts'), | |
|
1938 | 1944 | (dosets, b'sets'), |
|
1939 | 1945 | (domixed, b'mixed') |
|
1940 | 1946 | ] |
@@ -1209,7 +1209,7 b' class _lrucachenode(object):' | |||
|
1209 | 1209 | Holds a reference to nodes on either side as well as a key-value |
|
1210 | 1210 | pair for the dictionary entry. |
|
1211 | 1211 | """ |
|
1212 | __slots__ = (u'next', u'prev', u'key', u'value') | |
|
1212 | __slots__ = (u'next', u'prev', u'key', u'value', u'cost') | |
|
1213 | 1213 | |
|
1214 | 1214 | def __init__(self): |
|
1215 | 1215 | self.next = None |
@@ -1217,10 +1217,13 b' class _lrucachenode(object):' | |||
|
1217 | 1217 | |
|
1218 | 1218 | self.key = _notset |
|
1219 | 1219 | self.value = None |
|
1220 | self.cost = 0 | |
|
1220 | 1221 | |
|
1221 | 1222 | def markempty(self): |
|
1222 | 1223 | """Mark the node as emptied.""" |
|
1223 | 1224 | self.key = _notset |
|
1225 | self.value = None | |
|
1226 | self.cost = 0 | |
|
1224 | 1227 | |
|
1225 | 1228 | class lrucachedict(object): |
|
1226 | 1229 | """Dict that caches most recent accesses and sets. |
@@ -1233,6 +1236,10 b' class lrucachedict(object):' | |||
|
1233 | 1236 | we recycle head.prev and make it the new head. Cache accesses result in |
|
1234 | 1237 | the node being moved to before the existing head and being marked as the |
|
1235 | 1238 | new head node. |
|
1239 | ||
|
1240 | Items in the cache can be inserted with an optional "cost" value. This is | |
|
1241 | simply an integer that is specified by the caller. The cache can be queried | |
|
1242 | for the total cost of all items presently in the cache. | |
|
1236 | 1243 | """ |
|
1237 | 1244 | def __init__(self, max): |
|
1238 | 1245 | self._cache = {} |
@@ -1242,6 +1249,7 b' class lrucachedict(object):' | |||
|
1242 | 1249 | head.next = head |
|
1243 | 1250 | self._size = 1 |
|
1244 | 1251 | self.capacity = max |
|
1252 | self.totalcost = 0 | |
|
1245 | 1253 | |
|
1246 | 1254 | def __len__(self): |
|
1247 | 1255 | return len(self._cache) |
@@ -1261,11 +1269,15 b' class lrucachedict(object):' | |||
|
1261 | 1269 | self._movetohead(node) |
|
1262 | 1270 | return node.value |
|
1263 | 1271 | |
|
1264 |
def |
|
|
1272 | def insert(self, k, v, cost=0): | |
|
1273 | """Insert a new item in the cache with optional cost value.""" | |
|
1265 | 1274 | node = self._cache.get(k) |
|
1266 | 1275 | # Replace existing value and mark as newest. |
|
1267 | 1276 | if node is not None: |
|
1277 | self.totalcost -= node.cost | |
|
1268 | 1278 | node.value = v |
|
1279 | node.cost = cost | |
|
1280 | self.totalcost += cost | |
|
1269 | 1281 | self._movetohead(node) |
|
1270 | 1282 | return |
|
1271 | 1283 | |
@@ -1277,17 +1289,24 b' class lrucachedict(object):' | |||
|
1277 | 1289 | |
|
1278 | 1290 | # At capacity. Kill the old entry. |
|
1279 | 1291 | if node.key is not _notset: |
|
1292 | self.totalcost -= node.cost | |
|
1280 | 1293 | del self._cache[node.key] |
|
1281 | 1294 | |
|
1282 | 1295 | node.key = k |
|
1283 | 1296 | node.value = v |
|
1297 | node.cost = cost | |
|
1298 | self.totalcost += cost | |
|
1284 | 1299 | self._cache[k] = node |
|
1285 | 1300 | # And mark it as newest entry. No need to adjust order since it |
|
1286 | 1301 | # is already self._head.prev. |
|
1287 | 1302 | self._head = node |
|
1288 | 1303 | |
|
1304 | def __setitem__(self, k, v): | |
|
1305 | self.insert(k, v) | |
|
1306 | ||
|
1289 | 1307 | def __delitem__(self, k): |
|
1290 | 1308 | node = self._cache.pop(k) |
|
1309 | self.totalcost -= node.cost | |
|
1291 | 1310 | node.markempty() |
|
1292 | 1311 | |
|
1293 | 1312 | # Temporarily mark as newest item before re-adjusting head to make |
@@ -1306,6 +1325,7 b' class lrucachedict(object):' | |||
|
1306 | 1325 | def clear(self): |
|
1307 | 1326 | n = self._head |
|
1308 | 1327 | while n.key is not _notset: |
|
1328 | self.totalcost -= n.cost | |
|
1309 | 1329 | n.markempty() |
|
1310 | 1330 | n = n.next |
|
1311 | 1331 | |
@@ -1336,7 +1356,7 b' class lrucachedict(object):' | |||
|
1336 | 1356 | # We could potentially skip the first N items when decreasing capacity. |
|
1337 | 1357 | # But let's keep it simple unless it is a performance problem. |
|
1338 | 1358 | for i in range(len(self._cache)): |
|
1339 |
result |
|
|
1359 | result.insert(n.key, n.value, cost=n.cost) | |
|
1340 | 1360 | n = n.prev |
|
1341 | 1361 | |
|
1342 | 1362 | return result |
@@ -1359,6 +1379,7 b' class lrucachedict(object):' | |||
|
1359 | 1379 | |
|
1360 | 1380 | # And remove it from the cache and mark it as empty. |
|
1361 | 1381 | del self._cache[n.key] |
|
1382 | self.totalcost -= n.cost | |
|
1362 | 1383 | n.markempty() |
|
1363 | 1384 | |
|
1364 | 1385 | return key, value |
@@ -12,27 +12,33 b' class testlrucachedict(unittest.TestCase' | |||
|
12 | 12 | def testsimple(self): |
|
13 | 13 | d = util.lrucachedict(4) |
|
14 | 14 | self.assertEqual(d.capacity, 4) |
|
15 |
d |
|
|
15 | d.insert('a', 'va', cost=2) | |
|
16 | 16 | d['b'] = 'vb' |
|
17 | 17 | d['c'] = 'vc' |
|
18 | d['d'] = 'vd' | |
|
18 | d.insert('d', 'vd', cost=42) | |
|
19 | 19 | |
|
20 | 20 | self.assertEqual(d['a'], 'va') |
|
21 | 21 | self.assertEqual(d['b'], 'vb') |
|
22 | 22 | self.assertEqual(d['c'], 'vc') |
|
23 | 23 | self.assertEqual(d['d'], 'vd') |
|
24 | 24 | |
|
25 | self.assertEqual(d.totalcost, 44) | |
|
26 | ||
|
25 | 27 | # 'a' should be dropped because it was least recently used. |
|
26 | 28 | d['e'] = 've' |
|
27 | 29 | self.assertNotIn('a', d) |
|
28 | ||
|
29 | 30 | self.assertIsNone(d.get('a')) |
|
31 | self.assertEqual(d.totalcost, 42) | |
|
30 | 32 | |
|
31 | 33 | self.assertEqual(d['b'], 'vb') |
|
32 | 34 | self.assertEqual(d['c'], 'vc') |
|
33 | 35 | self.assertEqual(d['d'], 'vd') |
|
34 | 36 | self.assertEqual(d['e'], 've') |
|
35 | 37 | |
|
38 | # Replacing item with different cost adjusts totalcost. | |
|
39 | d.insert('e', 've', cost=4) | |
|
40 | self.assertEqual(d.totalcost, 46) | |
|
41 | ||
|
36 | 42 | # Touch entries in some order (both get and set). |
|
37 | 43 | d['e'] |
|
38 | 44 | d['c'] = 'vc2' |
@@ -63,12 +69,13 b' class testlrucachedict(unittest.TestCase' | |||
|
63 | 69 | |
|
64 | 70 | def testcopypartial(self): |
|
65 | 71 | d = util.lrucachedict(4) |
|
66 |
d |
|
|
67 |
d |
|
|
72 | d.insert('a', 'va', cost=4) | |
|
73 | d.insert('b', 'vb', cost=2) | |
|
68 | 74 | |
|
69 | 75 | dc = d.copy() |
|
70 | 76 | |
|
71 | 77 | self.assertEqual(len(dc), 2) |
|
78 | self.assertEqual(dc.totalcost, 6) | |
|
72 | 79 | for key in ('a', 'b'): |
|
73 | 80 | self.assertIn(key, dc) |
|
74 | 81 | self.assertEqual(dc[key], 'v%s' % key) |
@@ -80,8 +87,10 b' class testlrucachedict(unittest.TestCase' | |||
|
80 | 87 | |
|
81 | 88 | d['c'] = 'vc' |
|
82 | 89 | del d['b'] |
|
90 | self.assertEqual(d.totalcost, 4) | |
|
83 | 91 | dc = d.copy() |
|
84 | 92 | self.assertEqual(len(dc), 2) |
|
93 | self.assertEqual(dc.totalcost, 4) | |
|
85 | 94 | for key in ('a', 'c'): |
|
86 | 95 | self.assertIn(key, dc) |
|
87 | 96 | self.assertEqual(dc[key], 'v%s' % key) |
@@ -93,7 +102,7 b' class testlrucachedict(unittest.TestCase' | |||
|
93 | 102 | |
|
94 | 103 | def testcopyfull(self): |
|
95 | 104 | d = util.lrucachedict(4) |
|
96 | d['a'] = 'va' | |
|
105 | d.insert('a', 'va', cost=42) | |
|
97 | 106 | d['b'] = 'vb' |
|
98 | 107 | d['c'] = 'vc' |
|
99 | 108 | d['d'] = 'vd' |
@@ -104,6 +113,9 b' class testlrucachedict(unittest.TestCase' | |||
|
104 | 113 | self.assertIn(key, dc) |
|
105 | 114 | self.assertEqual(dc[key], 'v%s' % key) |
|
106 | 115 | |
|
116 | self.assertEqual(d.totalcost, 42) | |
|
117 | self.assertEqual(dc.totalcost, 42) | |
|
118 | ||
|
107 | 119 | # 'a' should be dropped because it was least recently used. |
|
108 | 120 | dc['e'] = 've' |
|
109 | 121 | self.assertNotIn('a', dc) |
@@ -111,6 +123,9 b' class testlrucachedict(unittest.TestCase' | |||
|
111 | 123 | self.assertIn(key, dc) |
|
112 | 124 | self.assertEqual(dc[key], 'v%s' % key) |
|
113 | 125 | |
|
126 | self.assertEqual(d.totalcost, 42) | |
|
127 | self.assertEqual(dc.totalcost, 0) | |
|
128 | ||
|
114 | 129 | # Contents and order of original dict should remain unchanged. |
|
115 | 130 | dc['b'] = 'vb_new' |
|
116 | 131 | |
@@ -120,25 +135,28 b' class testlrucachedict(unittest.TestCase' | |||
|
120 | 135 | |
|
121 | 136 | def testcopydecreasecapacity(self): |
|
122 | 137 | d = util.lrucachedict(5) |
|
123 |
d |
|
|
124 |
d |
|
|
138 | d.insert('a', 'va', cost=4) | |
|
139 | d.insert('b', 'vb', cost=2) | |
|
125 | 140 | d['c'] = 'vc' |
|
126 | 141 | d['d'] = 'vd' |
|
127 | 142 | |
|
128 | 143 | dc = d.copy(2) |
|
144 | self.assertEqual(dc.totalcost, 0) | |
|
129 | 145 | for key in ('a', 'b'): |
|
130 | 146 | self.assertNotIn(key, dc) |
|
131 | 147 | for key in ('c', 'd'): |
|
132 | 148 | self.assertIn(key, dc) |
|
133 | 149 | self.assertEqual(dc[key], 'v%s' % key) |
|
134 | 150 | |
|
135 |
dc |
|
|
151 | dc.insert('e', 've', cost=7) | |
|
152 | self.assertEqual(dc.totalcost, 7) | |
|
136 | 153 | self.assertNotIn('c', dc) |
|
137 | 154 | for key in ('d', 'e'): |
|
138 | 155 | self.assertIn(key, dc) |
|
139 | 156 | self.assertEqual(dc[key], 'v%s' % key) |
|
140 | 157 | |
|
141 | 158 | # Original should remain unchanged. |
|
159 | self.assertEqual(d.totalcost, 6) | |
|
142 | 160 | for key in ('a', 'b', 'c', 'd'): |
|
143 | 161 | self.assertIn(key, d) |
|
144 | 162 | self.assertEqual(d[key], 'v%s' % key) |
@@ -174,14 +192,16 b' class testlrucachedict(unittest.TestCase' | |||
|
174 | 192 | |
|
175 | 193 | def testpopoldest(self): |
|
176 | 194 | d = util.lrucachedict(4) |
|
177 | d['a'] = 'va' | |
|
178 |
d |
|
|
195 | d.insert('a', 'va', cost=10) | |
|
196 | d.insert('b', 'vb', cost=5) | |
|
179 | 197 | |
|
180 | 198 | self.assertEqual(len(d), 2) |
|
181 | 199 | self.assertEqual(d.popoldest(), ('a', 'va')) |
|
182 | 200 | self.assertEqual(len(d), 1) |
|
201 | self.assertEqual(d.totalcost, 5) | |
|
183 | 202 | self.assertEqual(d.popoldest(), ('b', 'vb')) |
|
184 | 203 | self.assertEqual(len(d), 0) |
|
204 | self.assertEqual(d.totalcost, 0) | |
|
185 | 205 | self.assertIsNone(d.popoldest()) |
|
186 | 206 | |
|
187 | 207 | d['a'] = 'va' |
General Comments 0
You need to be logged in to leave comments.
Login now