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 |
|
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 |
|
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 |
|
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 |
|
72 | d.insert('a', 'va', cost=4) | |
67 |
d |
|
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 |
|
138 | d.insert('a', 'va', cost=4) | |
124 |
d |
|
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 |
|
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 |
|
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