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