Rev 4297: Restore the ability to handle None as a key. in lp:///~jameinel/bzr/1.15-lru-gc
John Arbash Meinel
john at arbash-meinel.com
Thu Apr 16 23:07:01 BST 2009
At lp:///~jameinel/bzr/1.15-lru-gc
------------------------------------------------------------
revno: 4297
revision-id: john at arbash-meinel.com-20090416220625-hejrugy3r5qwlut9
parent: john at arbash-meinel.com-20090416205818-arnw8c6tvnjrs3h5
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: 1.15-lru-gc
timestamp: Thu 2009-04-16 17:06:25 -0500
message:
Restore the ability to handle None as a key.
We now use _null_key instead of None to indicate the end-of-refs.
This means we now check that _null_key isn't used as an actual key.
This slows us down from 7.1 => 7.3s or so.
Interestingly, the globals lookup of _null_key was faster than
node is self._lru (7.5s+). I was a bit surprised at that.
-------------- next part --------------
=== modified file 'bzrlib/lru_cache.py'
--- a/bzrlib/lru_cache.py 2009-04-16 20:58:18 +0000
+++ b/bzrlib/lru_cache.py 2009-04-16 22:06:25 +0000
@@ -21,6 +21,7 @@
trace,
)
+_null_key = object()
class _LRUNode(object):
"""This maintains the linked-list which is the lru internals."""
@@ -29,7 +30,7 @@
def __init__(self, key, value, cleanup=None):
self.prev = None
- self.next_key = None
+ self.next_key = _null_key
self.key = key
self.value = value
self.cleanup = cleanup
@@ -89,7 +90,9 @@
# Remove this node from the old location
node_prev = node.prev
next_key = node.next_key
- if next_key is None:
+ # benchmarking shows that the lookup of _null_key in globals is faster
+ # than the attribute lookup for (node is self._last_recently_used)
+ if next_key is _null_key:
# 'node' is the _last_recently_used, because it doesn't have a
# 'next' item. So move the current lru to the previous node.
self._last_recently_used = node_prev
@@ -116,7 +119,7 @@
' supposed to have a previous entry'
' %s' % (node,))
while node is not None:
- if node.next_key is None:
+ if node.next_key is _null_key:
if node is not self._last_recently_used:
raise AssertionError('only the last node should have'
' no next value: %s' % (node,))
@@ -149,9 +152,8 @@
:param cleanup: None or a function taking (key, value) to indicate
'value' should be cleaned up.
"""
- if key is None:
- # We use None to indicate non-entries in our key following code.
- raise ValueError('LRUCache cannot map None as a key')
+ if key is _null_key:
+ raise ValueError('cannot use _null_key as a key')
if key in self._cache:
node = self._cache[key]
node.run_cleanup()
@@ -223,7 +225,7 @@
self._last_recently_used = node.prev
if node.prev is not None:
node.prev.next_key = node.next_key
- if node.next_key is not None:
+ if node.next_key is not _null_key:
node_next = self._cache[node.next_key]
node_next.prev = node.prev
# INSERT
@@ -243,12 +245,12 @@
# Now remove this node from the linked list
if node.prev is not None:
node.prev.next_key = node.next_key
- if node.next_key is not None:
+ if node.next_key is not _null_key:
node_next = self._cache[node.next_key]
node_next.prev = node.prev
# And remove this node's pointers
node.prev = None
- node.next_key = None
+ node.next_key = _null_key
def _remove_lru(self):
"""Remove one entry from the lru, and handle consequences.
@@ -322,6 +324,8 @@
:param cleanup: None or a function taking (key, value) to indicate
'value' should be cleaned up.
"""
+ if key is _null_key:
+ raise ValueError('cannot use _null_key as a key')
node = self._cache.get(key, None)
value_len = self._compute_size(value)
if value_len >= self._after_cleanup_size:
=== modified file 'bzrlib/tests/test_lru_cache.py'
--- a/bzrlib/tests/test_lru_cache.py 2009-04-16 20:58:18 +0000
+++ b/bzrlib/tests/test_lru_cache.py 2009-04-16 22:06:25 +0000
@@ -46,6 +46,27 @@
self.failUnless('foo' in cache)
self.failIf('bar' in cache)
+ def test_map_None(self):
+ # Make sure that we can properly map None as a key.
+ cache = lru_cache.LRUCache(max_cache=10)
+ self.failIf(None in cache)
+ cache[None] = 1
+ self.assertEqual(1, cache[None])
+ cache[None] = 2
+ self.assertEqual(2, cache[None])
+ # Test the various code paths of __getitem__, to make sure that we can
+ # handle when None is the key for the LRU and the MRU
+ cache[1] = 3
+ cache[None] = 1
+ cache[None]
+ cache[1]
+ cache[None]
+ self.assertEqual([None, 1], [n.key for n in cache._walk_lru()])
+
+ def test_add__null_key(self):
+ cache = lru_cache.LRUCache(max_cache=10)
+ self.assertRaises(ValueError, cache.add, lru_cache._null_key, 1)
+
def test_overflow(self):
"""Adding extra entries will pop out old ones."""
cache = lru_cache.LRUCache(max_cache=1, after_cleanup_count=1)
@@ -279,6 +300,10 @@
self.assertEqual(int(cache._max_size*0.8), cache._after_cleanup_size)
self.assertEqual(0, cache._value_size)
+ def test_add__null_key(self):
+ cache = lru_cache.LRUSizeCache()
+ self.assertRaises(ValueError, cache.add, lru_cache._null_key, 1)
+
def test_add_tracks_size(self):
cache = lru_cache.LRUSizeCache()
self.assertEqual(0, cache._value_size)
More information about the bazaar-commits
mailing list