##// END OF EJS Templates
util: allow lrucachedict to track cost of entries...
Gregory Szorc -
r39603:ee087f0d default
parent child Browse files
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 __setitem__(self, k, v):
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[n.key] = n.value
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['a'] = 'va'
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['a'] = 'va'
67 d['b'] = 'vb'
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['a'] = 'va'
124 d['b'] = 'vb'
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['e'] = 've'
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['b'] = 'vb'
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