Merge lp:~jameinel/bzr/2.1-static-tuple-chk-map into lp:bzr

Proposed by John A Meinel
Status: Merged
Approved by: Andrew Bennetts
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~jameinel/bzr/2.1-static-tuple-chk-map
Merge into: lp:bzr
Diff against target: 1246 lines
10 files modified
NEWS (+9/-8)
bzrlib/_chk_map_py.py (+4/-3)
bzrlib/_chk_map_pyx.pyx (+41/-18)
bzrlib/_static_tuple_c.pxd (+5/-1)
bzrlib/chk_map.py (+99/-20)
bzrlib/groupcompress.py (+18/-1)
bzrlib/inventory.py (+28/-20)
bzrlib/repofmt/groupcompress_repo.py (+13/-4)
bzrlib/tests/test__chk_map.py (+22/-16)
bzrlib/tests/test_chk_map.py (+43/-70)
To merge this branch: bzr merge lp:~jameinel/bzr/2.1-static-tuple-chk-map
Reviewer Review Type Date Requested Status
Andrew Bennetts Approve
Review via email: mp+13740@code.launchpad.net
To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

This is an update to my earlier chk_map work (which Andrew had approved.)

The main changes are

1) I have benchmarked it as being specifically helpful. Namely it saves 30MB during 'bzr branch launchpad'. (548MB w/ bzr.dev => 518MB)

2) I changed it temporarily to require StaticTuples everywhere, and then updated the code in 'inventory.py' and 'groupcompress_repo.py' that were touching things directly.

3) I then updated the interface so that LeafNode and InternalNode and things underneath them require exactly StaticTuples, but CHKMap itself will cast to StaticTuple on demand. This seemed a fair way to migrate. Production code should never be poking at the internals of the nodes, and instead work via the CHKMap api.

This should help avoid spurious failures in the short term, and still encourage good habits in the long term.

Revision history for this message
Matt Nordhoff (mnordhoff) wrote :
Download full text (5.7 KiB)

I just tried this branch (well, + r4762 of bzr.dev), on my client and
server. Pushing to the server gave a traceback:

Thu 2009-10-22 03:35:30 +0000
0.357 bzr arguments: [u'serve', u'--inet', u'--directory=/',
u'--allow-writes']
0.372 looking for plugins in /home/mnordhoff/.bazaar/plugins
0.372 looking for plugins in /usr/local/co/bzr/bzr/bzr.dev/bzrlib/plugins
0.568 looking for plugins in
/usr/lib/python2.5/site-packages/bzrlib/plugins
0.571 encoding stdout as osutils.get_user_encoding() 'UTF-8'
2.439 bzr-svn: using Subversion 1.4.6 ()
10.883 Traceback (most recent call last):
  File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/smart/request.py", line
317, in _call_converting_errors
    return callable(*args, **kwargs)
  File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/smart/repository.py", line
727, in _inserter_thread
    stream, src_format, self.tokens)
  File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/repository.py", line 4242,
in insert_stream
    return self._locked_insert_stream(stream, src_format, is_resume)
  File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/repository.py", line 4343,
in _locked_insert_stream
    hint = self.target_repo.commit_write_group()
  File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/repository.py", line 1557,
in commit_write_group
    result = self._commit_write_group()
  File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/repofmt/pack_repo.py", line
2269, in _commit_write_group
    hint = self._pack_collection._commit_write_group()
  File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/repofmt/pack_repo.py", line
2097, in _commit_write_group
    problems = self._check_new_inventories()
  File
"/usr/local/co/bzr/bzr/bzr.dev/bzrlib/repofmt/groupcompress_repo.py",
line 653, in _check_new_inventories
    for record in _filter_text_keys(chk_diff, text_keys, bytes_to_info):
  File
"/usr/local/co/bzr/bzr/bzr.dev/bzrlib/repofmt/groupcompress_repo.py",
line 1189, in _filter_text_keys
    for record, items in interesting_nodes_iterable:
  File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 1642, in
process
    for record in self._read_all_roots():
  File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 1566, in
_read_all_roots
    self._read_nodes_from_store(new_keys):
  File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 1500, in
_read_nodes_from_store
    search_key_func=self._search_key_func)
  File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 1438, in
_deserialise
    search_key_func=search_key_func)
  File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 1023, in
deserialise
    ' StaticTuple not %s' % (type(key),))
AssertionError: deserialise should be called with a StaticTuple not
<type 'tuple'>

A local "bzr merge" did too:

Thu 2009-10-22 03:40:07 +0000
0.035 bzr arguments: [u'merge', u'/srv/bzr/bzr/statictuple-pickling/']
0.047 looking for plugins in /home/mnordhoff/.bazaar/plugins
0.047 looking for plugins in /usr/local/co/bzr/bzr/bzr.dev/bzrlib/plugins
0.162 looking for plugins in
/usr/lib/python2.5/site-packages/bzrlib/plugins
0.218 opening working tree '/usr/local/co/bzr/bzr/bzr.dev'
0.288 Using fetch logic to copy between
CHKInventoryRepository('file:///srv/bzr/bzr/.bzr/repository/')(<RepositoryF...

Read more...

Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (3.1 KiB)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Matt Nordhoff wrote:
> I just tried this branch (well, + r4762 of bzr.dev), on my client and
> server. Pushing to the server gave a traceback:

Thanks for the heads up.

...

> line 653, in _check_new_inventories
> for record in _filter_text_keys(chk_diff, text_keys, bytes_to_info):
> File
> "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/repofmt/groupcompress_repo.py",
> line 1189, in _filter_text_keys
> for record, items in interesting_nodes_iterable:
> File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 1642, in
> process
> for record in self._read_all_roots():
> File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 1566, in
> _read_all_roots
> self._read_nodes_from_store(new_keys):
> File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 1500, in
> _read_nodes_from_store
> search_key_func=self._search_key_func)
> File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 1438, in
> _deserialise
> search_key_func=search_key_func)
> File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 1023, in
> deserialise
> ' StaticTuple not %s' % (type(key),))
> AssertionError: deserialise should be called with a StaticTuple not
> <type 'tuple'>

I'll try to track down where the plain 'tuple' object came into play,
and also why I didn't catch it with the test suite. Admittedly I only
ran a subset, but I thought I ran "selftest -s bt.per_repo' which should
have covered this.

...

> for result in self.target.inventory.iter_changes(self.source.inventory):
> File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/inventory.py", line 2085,
> in iter_changes
> self.id_to_entry.iter_changes(basis.id_to_entry):
> File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 294, in
> iter_changes
> self._ensure_root()
> File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 140, in
> _ensure_root
> self._root_node = self._get_node(self._root_node)
> File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 155, in
> _get_node
> search_key_func=self._search_key_func)
> File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 1421, in
> _deserialise
> search_key_func=search_key_func)
> File "/usr/local/co/bzr/bzr/bzr.dev/bzrlib/chk_map.py", line 1008, in
> deserialise
> search_key_func=search_key_func)
> File "_chk_map_pyx.pyx", line 368, in
> _chk_map_pyx._deserialise_internal_node
> TypeError: key ('sha1:e12a0f03e145bbabf18cc7b933cce82edfc005dd',) is not
> a StaticTuple
>
> Turning off plugins did not help the latter one; I didn't try it with
> the first one.

^- This is pretty surprising, I'll certainly give it a look. Namely, it
looks like the root_key in 'basis.id_to_entry' is not a StaticTuple,
which is surprising given that the code that sets the root key has:
if type(node) is tuple:
    node = StaticTuple.from_sequence(node)

Thanks for looking closely.

John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkrgd48ACgkQJdeBCYSNAAOyVgCeIVTtO7/TDyp9nEv9CBUX4oq+
SM0An09eTMf4fXhfMSWtHcj48HGxKTQl
=oe2X
-----END PGP SIG...

Read more...

Revision history for this message
John A Meinel (jameinel) wrote :

pulling this back out since it seems to have failures, and I certainly can't trust PQM to catch them right now :)

Revision history for this message
John A Meinel (jameinel) wrote :

Unfortunately, I'm not able to reproduce either of Matt's failures. At this point, *my* best guess is that he had not recompiled the extensions yet, so it wasn't getting all of the code paths corrected. I certainly could be wrong, but I haven't found how yet.

Revision history for this message
Matt Nordhoff (mnordhoff) wrote :

John A Meinel wrote:
> Unfortunately, I'm not able to reproduce either of Matt's failures. At this point, *my* best guess is that he had not recompiled the extensions yet, so it wasn't getting all of the code paths corrected. I certainly could be wrong, but I haven't found how yet.

That's totally possible. At the time, keeping track of which changes I
was making to bzr.dev was really starting to drive me batty.

Making sure to recompile everything, I just tried to reproduce the
"merge" error and couldn't.

Pushing to a test branch (since I don't have any other revisions to
push), I can't reproduce that error, either.

...Ah, I found some real data to push. Still can't reproduce it.

Sorry for the false alarm.
--

Revision history for this message
John A Meinel (jameinel) wrote :

Ok, so I'm resubmitting this one again, only this time, it's even better.

Including the changes to CHKMap to only use StaticTuple internally, I also found that _filter_text_keys was creating a whole lot of file_key tuples, and not interning the various attributes.

I also found that we create *millions* of integer objects, and most of them are redundant because of the identical 'group' start and end information. (IIRC, we create 1.4M integers at peak of parsing the chk stream, and only 300k of them are unique.)

the _filter_text_keys fix saved around 40MB peak, interning the integers saves another 7MB.

Overall, with this patch, I'm now down to 457MB peak when branching all of launchpad. Which is very close to my 50% goal. I also know a way to save another ~10MB or so, but it requires using SimpleSet, which I'm not sure I want to do yet.

Anyway, versus bzr.dev, this patch drops me from 548MB => 457MB peak memory.

Also, I've focused a bit on 'streaming' data out of a repository (versus the insert on the other side). In that scenario, the numbers are:
  583MB bzr 2.0.1
  422MB bzr.dev
  338MB this patch

So not quite 50% savings, but I expect it to still be fairly noticable on Launchpad's code hosting.

Revision history for this message
Andrew Bennetts (spiv) wrote :

Looks ok, it's mostly mechanical despite the large line count. A couple of questions:

772 - self.parent_id_basename_to_file_id.key())
773 + (self.parent_id_basename_to_file_id.key()[0],))

That change (and the other similar ones looks a bit odd.

Oh, is that how you're casting a StaticTuple to a tuple for string formatting? I would have thought "x.as_tuple()" would be about as fast as "(x[0],)", and it's certainly more readable. I suppose it is noticeably slower?

1115 -class TestSearchKeyFuncs(tests.TestCase):

Why delete this TestCase?

review: Approve
Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Andrew Bennetts wrote:
> Review: Approve
> Looks ok, it's mostly mechanical despite the large line count. A couple of questions:
>
> 772 - self.parent_id_basename_to_file_id.key())
> 773 + (self.parent_id_basename_to_file_id.key()[0],))
>
> That change (and the other similar ones looks a bit odd.

The old code was formatting with:

"foo %s" % key

Which happened to work because 'key' was a tuple, but I don't think it
was actually *intentional*. I think it was written thinking that the
object was a string / simply formatted. I have the habit of always using
%(,) formatting, just in case the object I'm using happens to end up as
a tuple.
And the rest is just returning the simple sha1: string, rather than a
'tuple' (or StaticTuple).

I can certainly use '.as_tuple()' if you feel that is better. I've
always felt that the string formatting syntax *should* be
'str %s' % (arg1, arg2)

To make sure you don't pass an arg you don't quite understand. Sort of
the same idea that you should never do:

fprintf(file, str)

but alway

fprintf(file, "%s", str)

the former will often work, until it breaks horribly.

>
> Oh, is that how you're casting a StaticTuple to a tuple for string formatting? I would have thought "x.as_tuple()" would be about as fast as "(x[0],)", and it's certainly more readable. I suppose it is noticeably slower?
>
> 1115 -class TestSearchKeyFuncs(tests.TestCase):
>
> Why delete this TestCase?

Because all of the tests are covered in the "test__chk_map" which is
also permuted against the C and python versions of _chk_map

John
=;->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkrlAgIACgkQJdeBCYSNAAMnsQCbBdRB5bnNB4CxRTjgL9Gwy5wY
sowAnjco9oE2eSg6i03FOXgmHQUdtRPa
=/cVN
-----END PGP SIGNATURE-----

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Andrew Bennetts wrote:
> Review: Approve
> Looks ok, it's mostly mechanical despite the large line count. A couple of questions:
>
> 772 - self.parent_id_basename_to_file_id.key())
> 773 + (self.parent_id_basename_to_file_id.key()[0],))
>
> That change (and the other similar ones looks a bit odd.
>
> Oh, is that how you're casting a StaticTuple to a tuple for string formatting? I would have thought "x.as_tuple()" would be about as fast as "(x[0],)", and it's certainly more readable. I suppose it is noticeably slower?
>
> 1115 -class TestSearchKeyFuncs(tests.TestCase):
>
> Why delete this TestCase?

I'll mention, I'm not particularly comfortable landing this until we
sort how to get PQM actually failing appropriately again. I'm pretty
sure it is good, and I'll run the tests here, but it is a more-invasive
change. I suppose Babune will give me the real fallout?

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkrlAjIACgkQJdeBCYSNAANgyQCZARSGYStiFrFUxiNnSsyoW2Si
MzoAn10O3EG6v2BN00kMZrRApjZ7SZ3e
=pRWD
-----END PGP SIGNATURE-----

Revision history for this message
Robert Collins (lifeless) wrote :

On Mon, 2009-10-26 at 02:00 +0000, John A Meinel wrote:
>
>
> I'll mention, I'm not particularly comfortable landing this until we
> sort how to get PQM actually failing appropriately again. I'm pretty
> sure it is good, and I'll run the tests here, but it is a
> more-invasive
> change. I suppose Babune will give me the real fallout?

Yes, it will.

-Rob

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-10-26 06:44:40 +0000
+++ NEWS 2009-10-26 14:59:15 +0000
@@ -46,6 +46,7 @@
46 (John Arbash Meinel)46 (John Arbash Meinel)
4747
48* Peak memory under certain operations has been reduced significantly.48* Peak memory under certain operations has been reduced significantly.
49 (eg, 'bzr branch launchpad standalone' is cut in half)
49 (John Arbash Meinel)50 (John Arbash Meinel)
5051
51Documentation52Documentation
@@ -74,14 +75,14 @@
74 (John Arbash Meinel)75 (John Arbash Meinel)
7576
76* ``bzrlib._static_tuple_c.StaticTuple`` is now available and used by77* ``bzrlib._static_tuple_c.StaticTuple`` is now available and used by
77 the btree index parser. This class functions similarly to ``tuple``78 the btree index parser and the chk map parser. This class functions
78 objects. However, it can only point to a limited collection of types.79 similarly to ``tuple`` objects. However, it can only point to a limited
79 (Currently StaticTuple, str, unicode, None, bool, int, long, float, and80 collection of types. (Currently StaticTuple, str, unicode, None, bool,
80 not subclasses). This allows us to remove it from the garbage collector81 int, long, float, but not subclasses). This allows us to remove it from
81 (it cannot be in a cycle), it also allows us to intern the objects. In82 the garbage collector (it cannot be in a cycle), it also allows us to
82 testing, this can reduce peak memory by 20-40%, and significantly83 intern the objects. In testing, this can reduce peak memory by 20-40%,
83 improve performance by removing objects from being inspected by the84 and significantly improve performance by removing objects from being
84 garbage collector. (John Arbash Meinel)85 inspected by the garbage collector. (John Arbash Meinel)
8586
86* ``GroupCompressBlock._ensure_content()`` will now release the87* ``GroupCompressBlock._ensure_content()`` will now release the
87 ``zlib.decompressobj()`` when the first request is for all of the88 ``zlib.decompressobj()`` when the first request is for all of the
8889
=== modified file 'bzrlib/_chk_map_py.py'
--- bzrlib/_chk_map_py.py 2009-10-08 04:35:01 +0000
+++ bzrlib/_chk_map_py.py 2009-10-26 14:59:15 +0000
@@ -19,6 +19,8 @@
19import zlib19import zlib
20import struct20import struct
2121
22from bzrlib.static_tuple import StaticTuple
23
22_LeafNode = None24_LeafNode = None
23_InternalNode = None25_InternalNode = None
24_unknown = None26_unknown = None
@@ -93,7 +95,7 @@
93 value_lines = lines[pos:pos+num_value_lines]95 value_lines = lines[pos:pos+num_value_lines]
94 pos += num_value_lines96 pos += num_value_lines
95 value = '\n'.join(value_lines)97 value = '\n'.join(value_lines)
96 items[tuple(elements[:-1])] = value98 items[StaticTuple.from_sequence(elements[:-1])] = value
97 if len(items) != length:99 if len(items) != length:
98 raise AssertionError("item count (%d) mismatch for key %s,"100 raise AssertionError("item count (%d) mismatch for key %s,"
99 " bytes %r" % (length, key, bytes))101 " bytes %r" % (length, key, bytes))
@@ -141,7 +143,7 @@
141 for line in lines[5:]:143 for line in lines[5:]:
142 line = common_prefix + line144 line = common_prefix + line
143 prefix, flat_key = line.rsplit('\x00', 1)145 prefix, flat_key = line.rsplit('\x00', 1)
144 items[prefix] = (flat_key,)146 items[prefix] = StaticTuple(flat_key,)
145 if len(items) == 0:147 if len(items) == 0:
146 raise AssertionError("We didn't find any item for %s" % key)148 raise AssertionError("We didn't find any item for %s" % key)
147 result._items = items149 result._items = items
@@ -155,4 +157,3 @@
155 result._node_width = len(prefix)157 result._node_width = len(prefix)
156 result._search_prefix = common_prefix158 result._search_prefix = common_prefix
157 return result159 return result
158
159160
=== modified file 'bzrlib/_chk_map_pyx.pyx'
--- bzrlib/_chk_map_pyx.pyx 2009-10-08 04:35:01 +0000
+++ bzrlib/_chk_map_pyx.pyx 2009-10-26 14:59:15 +0000
@@ -29,9 +29,8 @@
2929
30cdef extern from "Python.h":30cdef extern from "Python.h":
31 ctypedef int Py_ssize_t # Required for older pyrex versions31 ctypedef int Py_ssize_t # Required for older pyrex versions
32 struct _PyObject:32 ctypedef struct PyObject:
33 pass33 pass
34 ctypedef _PyObject PyObject
35 int PyTuple_CheckExact(object p)34 int PyTuple_CheckExact(object p)
36 Py_ssize_t PyTuple_GET_SIZE(object t)35 Py_ssize_t PyTuple_GET_SIZE(object t)
37 int PyString_CheckExact(object)36 int PyString_CheckExact(object)
@@ -52,6 +51,18 @@
52 char *PyString_AS_STRING_ptr "PyString_AS_STRING" (PyObject *s)51 char *PyString_AS_STRING_ptr "PyString_AS_STRING" (PyObject *s)
53 object PyString_FromStringAndSize(char*, Py_ssize_t)52 object PyString_FromStringAndSize(char*, Py_ssize_t)
5453
54# cimport all of the definitions we will need to access
55from _static_tuple_c cimport StaticTuple,\
56 import_static_tuple_c, StaticTuple_New, \
57 StaticTuple_Intern, StaticTuple_SET_ITEM, StaticTuple_CheckExact
58
59cdef extern from "_static_tuple_c.h":
60 # Defined explicitly rather than cimport-ing. Trying to use cimport, the
61 # type for PyObject is a different class that happens to have the same
62 # name...
63 PyObject * StaticTuple_GET_ITEM_ptr "StaticTuple_GET_ITEM" (StaticTuple,
64 Py_ssize_t)
65
55cdef extern from "zlib.h":66cdef extern from "zlib.h":
56 ctypedef unsigned long uLong67 ctypedef unsigned long uLong
57 ctypedef unsigned int uInt68 ctypedef unsigned int uInt
@@ -60,8 +71,14 @@
60 uLong crc32(uLong crc, Bytef *buf, uInt len)71 uLong crc32(uLong crc, Bytef *buf, uInt len)
6172
6273
74# Set up the StaticTuple C_API functionality
75import_static_tuple_c()
76
77cdef object _LeafNode
63_LeafNode = None78_LeafNode = None
79cdef object _InternalNode
64_InternalNode = None80_InternalNode = None
81cdef object _unknown
65_unknown = None82_unknown = None
6683
67# We shouldn't just copy this from _dirstate_helpers_pyx84# We shouldn't just copy this from _dirstate_helpers_pyx
@@ -91,9 +108,9 @@
91 cdef char *c_out108 cdef char *c_out
92 cdef PyObject *bit109 cdef PyObject *bit
93110
94 if not PyTuple_CheckExact(key):111 if not StaticTuple_CheckExact(key):
95 raise TypeError('key %r is not a tuple' % (key,))112 raise TypeError('key %r is not a StaticTuple' % (key,))
96 num_bits = PyTuple_GET_SIZE(key)113 num_bits = len(key)
97 # 4 bytes per crc32, and another 1 byte between bits114 # 4 bytes per crc32, and another 1 byte between bits
98 num_out_bytes = (9 * num_bits) - 1115 num_out_bytes = (9 * num_bits) - 1
99 out = PyString_FromStringAndSize(NULL, num_out_bytes)116 out = PyString_FromStringAndSize(NULL, num_out_bytes)
@@ -105,7 +122,7 @@
105 # We use the _ptr variant, because GET_ITEM returns a borrowed122 # We use the _ptr variant, because GET_ITEM returns a borrowed
106 # reference, and Pyrex assumes that returned 'object' are a new123 # reference, and Pyrex assumes that returned 'object' are a new
107 # reference124 # reference
108 bit = PyTuple_GET_ITEM_ptr(key, i)125 bit = StaticTuple_GET_ITEM_ptr(key, i)
109 if not PyString_CheckExact_ptr(bit):126 if not PyString_CheckExact_ptr(bit):
110 raise TypeError('Bit %d of %r is not a string' % (i, key))127 raise TypeError('Bit %d of %r is not a string' % (i, key))
111 c_bit = <Bytef *>PyString_AS_STRING_ptr(bit)128 c_bit = <Bytef *>PyString_AS_STRING_ptr(bit)
@@ -129,9 +146,9 @@
129 cdef char *c_out146 cdef char *c_out
130 cdef PyObject *bit147 cdef PyObject *bit
131148
132 if not PyTuple_CheckExact(key):149 if not StaticTuple_CheckExact(key):
133 raise TypeError('key %r is not a tuple' % (key,))150 raise TypeError('key %r is not a StaticTuple' % (key,))
134 num_bits = PyTuple_GET_SIZE(key)151 num_bits = len(key)
135 # 4 bytes per crc32, and another 1 byte between bits152 # 4 bytes per crc32, and another 1 byte between bits
136 num_out_bytes = (5 * num_bits) - 1153 num_out_bytes = (5 * num_bits) - 1
137 out = PyString_FromStringAndSize(NULL, num_out_bytes)154 out = PyString_FromStringAndSize(NULL, num_out_bytes)
@@ -140,10 +157,10 @@
140 if i > 0:157 if i > 0:
141 c_out[0] = c'\x00'158 c_out[0] = c'\x00'
142 c_out = c_out + 1159 c_out = c_out + 1
143 bit = PyTuple_GET_ITEM_ptr(key, i)160 bit = StaticTuple_GET_ITEM_ptr(key, i)
144 if not PyString_CheckExact_ptr(bit):161 if not PyString_CheckExact_ptr(bit):
145 raise TypeError('Bit %d of %r is not a string: %r' % (i, key,162 raise TypeError('Bit %d of %r is not a string: %r'
146 <object>bit))163 % (i, key, <object>bit))
147 c_bit = <Bytef *>PyString_AS_STRING_ptr(bit)164 c_bit = <Bytef *>PyString_AS_STRING_ptr(bit)
148 c_len = PyString_GET_SIZE_ptr(bit)165 c_len = PyString_GET_SIZE_ptr(bit)
149 crc_val = crc32(0, c_bit, c_len)166 crc_val = crc32(0, c_bit, c_len)
@@ -195,6 +212,7 @@
195 cdef char *prefix, *value_start, *prefix_tail212 cdef char *prefix, *value_start, *prefix_tail
196 cdef char *next_null, *last_null, *line_start213 cdef char *next_null, *last_null, *line_start
197 cdef char *c_entry, *entry_start214 cdef char *c_entry, *entry_start
215 cdef StaticTuple entry_bits
198216
199 if _LeafNode is None:217 if _LeafNode is None:
200 from bzrlib import chk_map218 from bzrlib import chk_map
@@ -265,12 +283,14 @@
265 if next_line == NULL:283 if next_line == NULL:
266 raise ValueError('missing trailing newline')284 raise ValueError('missing trailing newline')
267 cur = next_line + 1285 cur = next_line + 1
268 entry_bits = PyTuple_New(width)286 entry_bits = StaticTuple_New(width)
269 for i from 0 <= i < num_prefix_bits:287 for i from 0 <= i < num_prefix_bits:
288 # TODO: Use PyList_GetItem, or turn prefix_bits into a
289 # tuple/StaticTuple
270 entry = prefix_bits[i]290 entry = prefix_bits[i]
271 # SET_ITEM 'steals' a reference291 # SET_ITEM 'steals' a reference
272 Py_INCREF(entry)292 Py_INCREF(entry)
273 PyTuple_SET_ITEM(entry_bits, i, entry)293 StaticTuple_SET_ITEM(entry_bits, i, entry)
274 value = PyString_FromStringAndSize(value_start, next_line - value_start)294 value = PyString_FromStringAndSize(value_start, next_line - value_start)
275 # The next entry bit needs the 'tail' from the prefix, and first part295 # The next entry bit needs the 'tail' from the prefix, and first part
276 # of the line296 # of the line
@@ -288,7 +308,7 @@
288 memcpy(c_entry + prefix_tail_len, line_start, next_null - line_start)308 memcpy(c_entry + prefix_tail_len, line_start, next_null - line_start)
289 Py_INCREF(entry)309 Py_INCREF(entry)
290 i = num_prefix_bits310 i = num_prefix_bits
291 PyTuple_SET_ITEM(entry_bits, i, entry)311 StaticTuple_SET_ITEM(entry_bits, i, entry)
292 while next_null != last_null: # We have remaining bits312 while next_null != last_null: # We have remaining bits
293 i = i + 1313 i = i + 1
294 if i > width:314 if i > width:
@@ -301,11 +321,12 @@
301 entry = PyString_FromStringAndSize(entry_start,321 entry = PyString_FromStringAndSize(entry_start,
302 next_null - entry_start)322 next_null - entry_start)
303 Py_INCREF(entry)323 Py_INCREF(entry)
304 PyTuple_SET_ITEM(entry_bits, i, entry)324 StaticTuple_SET_ITEM(entry_bits, i, entry)
305 if len(entry_bits) != width:325 if len(entry_bits) != width:
306 raise AssertionError(326 raise AssertionError(
307 'Incorrect number of elements (%d vs %d)'327 'Incorrect number of elements (%d vs %d)'
308 % (len(entry_bits)+1, width + 1))328 % (len(entry_bits)+1, width + 1))
329 entry_bits = StaticTuple_Intern(entry_bits)
309 PyDict_SetItem(items, entry_bits, value)330 PyDict_SetItem(items, entry_bits, value)
310 if len(items) != length:331 if len(items) != length:
311 raise ValueError("item count (%d) mismatch for key %s,"332 raise ValueError("item count (%d) mismatch for key %s,"
@@ -343,6 +364,8 @@
343 _unknown = chk_map._unknown364 _unknown = chk_map._unknown
344 result = _InternalNode(search_key_func=search_key_func)365 result = _InternalNode(search_key_func=search_key_func)
345366
367 if not StaticTuple_CheckExact(key):
368 raise TypeError('key %r is not a StaticTuple' % (key,))
346 if not PyString_CheckExact(bytes):369 if not PyString_CheckExact(bytes):
347 raise TypeError('bytes must be a plain string not %s' % (type(bytes),))370 raise TypeError('bytes must be a plain string not %s' % (type(bytes),))
348371
@@ -384,7 +407,8 @@
384 memcpy(c_item_prefix + prefix_length, cur, next_null - cur)407 memcpy(c_item_prefix + prefix_length, cur, next_null - cur)
385 flat_key = PyString_FromStringAndSize(next_null + 1,408 flat_key = PyString_FromStringAndSize(next_null + 1,
386 next_line - next_null - 1)409 next_line - next_null - 1)
387 PyDict_SetItem(items, item_prefix, (flat_key,))410 flat_key = StaticTuple(flat_key).intern()
411 PyDict_SetItem(items, item_prefix, flat_key)
388 cur = next_line + 1412 cur = next_line + 1
389 assert len(items) > 0413 assert len(items) > 0
390 result._items = items414 result._items = items
@@ -398,4 +422,3 @@
398 result._node_width = len(item_prefix)422 result._node_width = len(item_prefix)
399 result._search_prefix = PyString_FromStringAndSize(prefix, prefix_length)423 result._search_prefix = PyString_FromStringAndSize(prefix, prefix_length)
400 return result424 return result
401
402425
=== modified file 'bzrlib/_static_tuple_c.pxd'
--- bzrlib/_static_tuple_c.pxd 2009-10-07 15:57:25 +0000
+++ bzrlib/_static_tuple_c.pxd 2009-10-26 14:59:15 +0000
@@ -36,5 +36,9 @@
3636
37 # Steals a reference and val must be a valid type, no checking is done37 # Steals a reference and val must be a valid type, no checking is done
38 void StaticTuple_SET_ITEM(StaticTuple key, Py_ssize_t offset, object val)38 void StaticTuple_SET_ITEM(StaticTuple key, Py_ssize_t offset, object val)
39 object StaticTuple_GET_ITEM(StaticTuple key, Py_ssize_t offset)39 # We would normally use PyObject * here. However it seems that cython/pyrex
40 # treat the PyObject defined in this header as something different than one
41 # defined in a .pyx file. And since we don't INCREF, we need a raw pointer,
42 # not an 'object' return value.
43 void *StaticTuple_GET_ITEM(StaticTuple key, Py_ssize_t offset)
40 int StaticTuple_CheckExact(object)44 int StaticTuple_CheckExact(object)
4145
=== modified file 'bzrlib/chk_map.py'
--- bzrlib/chk_map.py 2009-10-20 20:30:21 +0000
+++ bzrlib/chk_map.py 2009-10-26 14:59:15 +0000
@@ -52,6 +52,7 @@
52 registry,52 registry,
53 trace,53 trace,
54 )54 )
55from bzrlib.static_tuple import StaticTuple
5556
56# approx 4MB57# approx 4MB
57# If each line is 50 bytes, and you have 255 internal pages, with 255-way fan58# If each line is 50 bytes, and you have 255 internal pages, with 255-way fan
@@ -114,8 +115,9 @@
114 """115 """
115 delete_count = 0116 delete_count = 0
116 # Check preconditions first.117 # Check preconditions first.
117 new_items = set([key for (old, key, value) in delta if key is not None118 as_st = StaticTuple.from_sequence
118 and old is None])119 new_items = set([as_st(key) for (old, key, value) in delta
120 if key is not None and old is None])
119 existing_new = list(self.iteritems(key_filter=new_items))121 existing_new = list(self.iteritems(key_filter=new_items))
120 if existing_new:122 if existing_new:
121 raise errors.InconsistentDeltaDelta(delta,123 raise errors.InconsistentDeltaDelta(delta,
@@ -135,7 +137,7 @@
135137
136 def _ensure_root(self):138 def _ensure_root(self):
137 """Ensure that the root node is an object not a key."""139 """Ensure that the root node is an object not a key."""
138 if type(self._root_node) is tuple:140 if type(self._root_node) is StaticTuple:
139 # Demand-load the root141 # Demand-load the root
140 self._root_node = self._get_node(self._root_node)142 self._root_node = self._get_node(self._root_node)
141143
@@ -149,7 +151,7 @@
149 :param node: A tuple key or node object.151 :param node: A tuple key or node object.
150 :return: A node object.152 :return: A node object.
151 """153 """
152 if type(node) is tuple:154 if type(node) is StaticTuple:
153 bytes = self._read_bytes(node)155 bytes = self._read_bytes(node)
154 return _deserialise(bytes, node,156 return _deserialise(bytes, node,
155 search_key_func=self._search_key_func)157 search_key_func=self._search_key_func)
@@ -196,7 +198,7 @@
196 for key, value in sorted(node._items.iteritems()):198 for key, value in sorted(node._items.iteritems()):
197 # Don't use prefix nor indent here to line up when used in199 # Don't use prefix nor indent here to line up when used in
198 # tests in conjunction with assertEqualDiff200 # tests in conjunction with assertEqualDiff
199 result.append(' %r %r' % (key, value))201 result.append(' %r %r' % (tuple(key), value))
200 return result202 return result
201203
202 @classmethod204 @classmethod
@@ -220,6 +222,9 @@
220 root_key = klass._create_directly(store, initial_value,222 root_key = klass._create_directly(store, initial_value,
221 maximum_size=maximum_size, key_width=key_width,223 maximum_size=maximum_size, key_width=key_width,
222 search_key_func=search_key_func)224 search_key_func=search_key_func)
225 if type(root_key) is not StaticTuple:
226 raise AssertionError('we got a %s instead of a StaticTuple'
227 % (type(root_key),))
223 return root_key228 return root_key
224229
225 @classmethod230 @classmethod
@@ -240,9 +245,11 @@
240 node = LeafNode(search_key_func=search_key_func)245 node = LeafNode(search_key_func=search_key_func)
241 node.set_maximum_size(maximum_size)246 node.set_maximum_size(maximum_size)
242 node._key_width = key_width247 node._key_width = key_width
243 node._items = dict(initial_value)248 as_st = StaticTuple.from_sequence
249 node._items = dict([(as_st(key), val) for key, val
250 in initial_value.iteritems()])
244 node._raw_size = sum([node._key_value_len(key, value)251 node._raw_size = sum([node._key_value_len(key, value)
245 for key,value in initial_value.iteritems()])252 for key,value in node._items.iteritems()])
246 node._len = len(node._items)253 node._len = len(node._items)
247 node._compute_search_prefix()254 node._compute_search_prefix()
248 node._compute_serialised_prefix()255 node._compute_serialised_prefix()
@@ -484,11 +491,14 @@
484 def iteritems(self, key_filter=None):491 def iteritems(self, key_filter=None):
485 """Iterate over the entire CHKMap's contents."""492 """Iterate over the entire CHKMap's contents."""
486 self._ensure_root()493 self._ensure_root()
494 if key_filter is not None:
495 as_st = StaticTuple.from_sequence
496 key_filter = [as_st(key) for key in key_filter]
487 return self._root_node.iteritems(self._store, key_filter=key_filter)497 return self._root_node.iteritems(self._store, key_filter=key_filter)
488498
489 def key(self):499 def key(self):
490 """Return the key for this map."""500 """Return the key for this map."""
491 if type(self._root_node) is tuple:501 if type(self._root_node) is StaticTuple:
492 return self._root_node502 return self._root_node
493 else:503 else:
494 return self._root_node._key504 return self._root_node._key
@@ -503,6 +513,7 @@
503 :param key: A key to map.513 :param key: A key to map.
504 :param value: The value to assign to key.514 :param value: The value to assign to key.
505 """515 """
516 key = StaticTuple.from_sequence(key)
506 # Need a root object.517 # Need a root object.
507 self._ensure_root()518 self._ensure_root()
508 prefix, node_details = self._root_node.map(self._store, key, value)519 prefix, node_details = self._root_node.map(self._store, key, value)
@@ -519,12 +530,15 @@
519 def _node_key(self, node):530 def _node_key(self, node):
520 """Get the key for a node whether it's a tuple or node."""531 """Get the key for a node whether it's a tuple or node."""
521 if type(node) is tuple:532 if type(node) is tuple:
533 node = StaticTuple.from_sequence(node)
534 if type(node) is StaticTuple:
522 return node535 return node
523 else:536 else:
524 return node._key537 return node._key
525538
526 def unmap(self, key, check_remap=True):539 def unmap(self, key, check_remap=True):
527 """remove key from the map."""540 """remove key from the map."""
541 key = StaticTuple.from_sequence(key)
528 self._ensure_root()542 self._ensure_root()
529 if type(self._root_node) is InternalNode:543 if type(self._root_node) is InternalNode:
530 unmapped = self._root_node.unmap(self._store, key,544 unmapped = self._root_node.unmap(self._store, key,
@@ -544,7 +558,7 @@
544558
545 :return: The key of the root node.559 :return: The key of the root node.
546 """560 """
547 if type(self._root_node) is tuple:561 if type(self._root_node) is StaticTuple:
548 # Already saved.562 # Already saved.
549 return self._root_node563 return self._root_node
550 keys = list(self._root_node.serialise(self._store))564 keys = list(self._root_node.serialise(self._store))
@@ -881,7 +895,7 @@
881 lines.append(serialized[prefix_len:])895 lines.append(serialized[prefix_len:])
882 lines.extend(value_lines)896 lines.extend(value_lines)
883 sha1, _, _ = store.add_lines((None,), (), lines)897 sha1, _, _ = store.add_lines((None,), (), lines)
884 self._key = ("sha1:" + sha1,)898 self._key = StaticTuple("sha1:" + sha1,).intern()
885 bytes = ''.join(lines)899 bytes = ''.join(lines)
886 if len(bytes) != self._current_size():900 if len(bytes) != self._current_size():
887 raise AssertionError('Invalid _current_size')901 raise AssertionError('Invalid _current_size')
@@ -1004,6 +1018,9 @@
1004 :param key: The key that the serialised node has.1018 :param key: The key that the serialised node has.
1005 :return: An InternalNode instance.1019 :return: An InternalNode instance.
1006 """1020 """
1021 if type(key) is not StaticTuple:
1022 raise AssertionError('deserialise should be called with a'
1023 ' StaticTuple not %s' % (type(key),))
1007 return _deserialise_internal_node(bytes, key,1024 return _deserialise_internal_node(bytes, key,
1008 search_key_func=search_key_func)1025 search_key_func=search_key_func)
10091026
@@ -1034,7 +1051,7 @@
1034 # for whatever we are missing1051 # for whatever we are missing
1035 shortcut = True1052 shortcut = True
1036 for prefix, node in self._items.iteritems():1053 for prefix, node in self._items.iteritems():
1037 if node.__class__ is tuple:1054 if node.__class__ is StaticTuple:
1038 keys[node] = (prefix, None)1055 keys[node] = (prefix, None)
1039 else:1056 else:
1040 yield node, None1057 yield node, None
@@ -1069,7 +1086,7 @@
1069 # A given key can only match 1 child node, if it isn't1086 # A given key can only match 1 child node, if it isn't
1070 # there, then we can just return nothing1087 # there, then we can just return nothing
1071 return1088 return
1072 if node.__class__ is tuple:1089 if node.__class__ is StaticTuple:
1073 keys[node] = (search_prefix, [key])1090 keys[node] = (search_prefix, [key])
1074 else:1091 else:
1075 # This is loaded, and the only thing that can match,1092 # This is loaded, and the only thing that can match,
@@ -1102,7 +1119,7 @@
1102 # We can ignore this one1119 # We can ignore this one
1103 continue1120 continue
1104 node_key_filter = prefix_to_keys[search_prefix]1121 node_key_filter = prefix_to_keys[search_prefix]
1105 if node.__class__ is tuple:1122 if node.__class__ is StaticTuple:
1106 keys[node] = (search_prefix, node_key_filter)1123 keys[node] = (search_prefix, node_key_filter)
1107 else:1124 else:
1108 yield node, node_key_filter1125 yield node, node_key_filter
@@ -1117,7 +1134,7 @@
1117 if sub_prefix in length_filter:1134 if sub_prefix in length_filter:
1118 node_key_filter.extend(prefix_to_keys[sub_prefix])1135 node_key_filter.extend(prefix_to_keys[sub_prefix])
1119 if node_key_filter: # this key matched something, yield it1136 if node_key_filter: # this key matched something, yield it
1120 if node.__class__ is tuple:1137 if node.__class__ is StaticTuple:
1121 keys[node] = (prefix, node_key_filter)1138 keys[node] = (prefix, node_key_filter)
1122 else:1139 else:
1123 yield node, node_key_filter1140 yield node, node_key_filter
@@ -1255,7 +1272,7 @@
1255 :return: An iterable of the keys inserted by this operation.1272 :return: An iterable of the keys inserted by this operation.
1256 """1273 """
1257 for node in self._items.itervalues():1274 for node in self._items.itervalues():
1258 if type(node) is tuple:1275 if type(node) is StaticTuple:
1259 # Never deserialised.1276 # Never deserialised.
1260 continue1277 continue
1261 if node._key is not None:1278 if node._key is not None:
@@ -1272,7 +1289,7 @@
1272 lines.append('%s\n' % (self._search_prefix,))1289 lines.append('%s\n' % (self._search_prefix,))
1273 prefix_len = len(self._search_prefix)1290 prefix_len = len(self._search_prefix)
1274 for prefix, node in sorted(self._items.items()):1291 for prefix, node in sorted(self._items.items()):
1275 if type(node) is tuple:1292 if type(node) is StaticTuple:
1276 key = node[0]1293 key = node[0]
1277 else:1294 else:
1278 key = node._key[0]1295 key = node._key[0]
@@ -1282,7 +1299,7 @@
1282 % (serialised, self._search_prefix))1299 % (serialised, self._search_prefix))
1283 lines.append(serialised[prefix_len:])1300 lines.append(serialised[prefix_len:])
1284 sha1, _, _ = store.add_lines((None,), (), lines)1301 sha1, _, _ = store.add_lines((None,), (), lines)
1285 self._key = ("sha1:" + sha1,)1302 self._key = StaticTuple("sha1:" + sha1,).intern()
1286 _page_cache.add(self._key, ''.join(lines))1303 _page_cache.add(self._key, ''.join(lines))
1287 yield self._key1304 yield self._key
12881305
@@ -1317,7 +1334,7 @@
1317 raise AssertionError("unserialised nodes have no refs.")1334 raise AssertionError("unserialised nodes have no refs.")
1318 refs = []1335 refs = []
1319 for value in self._items.itervalues():1336 for value in self._items.itervalues():
1320 if type(value) is tuple:1337 if type(value) is StaticTuple:
1321 refs.append(value)1338 refs.append(value)
1322 else:1339 else:
1323 refs.append(value.key())1340 refs.append(value.key())
@@ -1437,6 +1454,12 @@
14371454
1438 def __init__(self, store, new_root_keys, old_root_keys,1455 def __init__(self, store, new_root_keys, old_root_keys,
1439 search_key_func, pb=None):1456 search_key_func, pb=None):
1457 # TODO: Should we add a StaticTuple barrier here? It would be nice to
1458 # force callers to use StaticTuple, because there will often be
1459 # lots of keys passed in here. And even if we cast it locally,
1460 # that just meanst that we will have *both* a StaticTuple and a
1461 # tuple() in memory, referring to the same object. (so a net
1462 # increase in memory, not a decrease.)
1440 self._store = store1463 self._store = store
1441 self._new_root_keys = new_root_keys1464 self._new_root_keys = new_root_keys
1442 self._old_root_keys = old_root_keys1465 self._old_root_keys = old_root_keys
@@ -1444,11 +1467,16 @@
1444 # All uninteresting chks that we have seen. By the time they are added1467 # All uninteresting chks that we have seen. By the time they are added
1445 # here, they should be either fully ignored, or queued up for1468 # here, they should be either fully ignored, or queued up for
1446 # processing1469 # processing
1470 # TODO: This might grow to a large size if there are lots of merge
1471 # parents, etc. However, it probably doesn't scale to O(history)
1472 # like _processed_new_refs does.
1447 self._all_old_chks = set(self._old_root_keys)1473 self._all_old_chks = set(self._old_root_keys)
1448 # All items that we have seen from the old_root_keys1474 # All items that we have seen from the old_root_keys
1449 self._all_old_items = set()1475 self._all_old_items = set()
1450 # These are interesting items which were either read, or already in the1476 # These are interesting items which were either read, or already in the
1451 # interesting queue (so we don't need to walk them again)1477 # interesting queue (so we don't need to walk them again)
1478 # TODO: processed_new_refs becomes O(all_chks), consider switching to
1479 # SimpleSet here.
1452 self._processed_new_refs = set()1480 self._processed_new_refs = set()
1453 self._search_key_func = search_key_func1481 self._search_key_func = search_key_func
14541482
@@ -1466,6 +1494,7 @@
1466 # this code. (We may want to evaluate saving the raw bytes into the1494 # this code. (We may want to evaluate saving the raw bytes into the
1467 # page cache, which would allow a working tree update after the fetch1495 # page cache, which would allow a working tree update after the fetch
1468 # to not have to read the bytes again.)1496 # to not have to read the bytes again.)
1497 as_st = StaticTuple.from_sequence
1469 stream = self._store.get_record_stream(keys, 'unordered', True)1498 stream = self._store.get_record_stream(keys, 'unordered', True)
1470 for record in stream:1499 for record in stream:
1471 if self._pb is not None:1500 if self._pb is not None:
@@ -1478,10 +1507,18 @@
1478 if type(node) is InternalNode:1507 if type(node) is InternalNode:
1479 # Note we don't have to do node.refs() because we know that1508 # Note we don't have to do node.refs() because we know that
1480 # there are no children that have been pushed into this node1509 # there are no children that have been pushed into this node
1510 # Note: Using as_st() here seemed to save 1.2MB, which would
1511 # indicate that we keep 100k prefix_refs around while
1512 # processing. They *should* be shorter lived than that...
1513 # It does cost us ~10s of processing time
1514 #prefix_refs = [as_st(item) for item in node._items.iteritems()]
1481 prefix_refs = node._items.items()1515 prefix_refs = node._items.items()
1482 items = []1516 items = []
1483 else:1517 else:
1484 prefix_refs = []1518 prefix_refs = []
1519 # Note: We don't use a StaticTuple here. Profiling showed a
1520 # minor memory improvement (0.8MB out of 335MB peak 0.2%)
1521 # But a significant slowdown (15s / 145s, or 10%)
1485 items = node._items.items()1522 items = node._items.items()
1486 yield record, node, prefix_refs, items1523 yield record, node, prefix_refs, items
14871524
@@ -1495,6 +1532,10 @@
1495 if p_r[1] not in all_old_chks]1532 if p_r[1] not in all_old_chks]
1496 new_refs = [p_r[1] for p_r in prefix_refs]1533 new_refs = [p_r[1] for p_r in prefix_refs]
1497 all_old_chks.update(new_refs)1534 all_old_chks.update(new_refs)
1535 # TODO: This might be a good time to turn items into StaticTuple
1536 # instances and possibly intern them. However, this does not
1537 # impact 'initial branch' performance, so I'm not worrying
1538 # about this yet
1498 self._all_old_items.update(items)1539 self._all_old_items.update(items)
1499 # Queue up the uninteresting references1540 # Queue up the uninteresting references
1500 # Don't actually put them in the 'to-read' queue until we have1541 # Don't actually put them in the 'to-read' queue until we have
@@ -1553,6 +1594,9 @@
1553 # current design allows for this, as callers will do the work1594 # current design allows for this, as callers will do the work
1554 # to make the results unique. We might profile whether we1595 # to make the results unique. We might profile whether we
1555 # gain anything by ensuring unique return values for items1596 # gain anything by ensuring unique return values for items
1597 # TODO: This might be a good time to cast to StaticTuple, as
1598 # self._new_item_queue will hold the contents of multiple
1599 # records for an extended lifetime
1556 new_items = [item for item in items1600 new_items = [item for item in items
1557 if item not in self._all_old_items]1601 if item not in self._all_old_items]
1558 self._new_item_queue.extend(new_items)1602 self._new_item_queue.extend(new_items)
@@ -1583,16 +1627,31 @@
1583 if new_items:1627 if new_items:
1584 yield None, new_items1628 yield None, new_items
1585 refs = refs.difference(all_old_chks)1629 refs = refs.difference(all_old_chks)
1630 processed_new_refs.update(refs)
1586 while refs:1631 while refs:
1632 # TODO: Using a SimpleSet for self._processed_new_refs and
1633 # saved as much as 10MB of peak memory. However, it requires
1634 # implementing a non-pyrex version.
1587 next_refs = set()1635 next_refs = set()
1588 next_refs_update = next_refs.update1636 next_refs_update = next_refs.update
1589 # Inlining _read_nodes_from_store improves 'bzr branch bzr.dev'1637 # Inlining _read_nodes_from_store improves 'bzr branch bzr.dev'
1590 # from 1m54s to 1m51s. Consider it.1638 # from 1m54s to 1m51s. Consider it.
1591 for record, _, p_refs, items in self._read_nodes_from_store(refs):1639 for record, _, p_refs, items in self._read_nodes_from_store(refs):
1592 items = [item for item in items1640 if all_old_items:
1593 if item not in all_old_items]1641 # using the 'if' check saves about 145s => 141s, when
1642 # streaming initial branch of Launchpad data.
1643 items = [item for item in items
1644 if item not in all_old_items]
1594 yield record, items1645 yield record, items
1595 next_refs_update([p_r[1] for p_r in p_refs])1646 next_refs_update([p_r[1] for p_r in p_refs])
1647 del p_refs
1648 # set1.difference(set/dict) walks all of set1, and checks if it
1649 # exists in 'other'.
1650 # set1.difference(iterable) walks all of iterable, and does a
1651 # 'difference_update' on a clone of set1. Pick wisely based on the
1652 # expected sizes of objects.
1653 # in our case it is expected that 'new_refs' will always be quite
1654 # small.
1596 next_refs = next_refs.difference(all_old_chks)1655 next_refs = next_refs.difference(all_old_chks)
1597 next_refs = next_refs.difference(processed_new_refs)1656 next_refs = next_refs.difference(processed_new_refs)
1598 processed_new_refs.update(next_refs)1657 processed_new_refs.update(next_refs)
@@ -1605,6 +1664,7 @@
1605 self._old_queue = []1664 self._old_queue = []
1606 all_old_chks = self._all_old_chks1665 all_old_chks = self._all_old_chks
1607 for record, _, prefix_refs, items in self._read_nodes_from_store(refs):1666 for record, _, prefix_refs, items in self._read_nodes_from_store(refs):
1667 # TODO: Use StaticTuple here?
1608 self._all_old_items.update(items)1668 self._all_old_items.update(items)
1609 refs = [r for _,r in prefix_refs if r not in all_old_chks]1669 refs = [r for _,r in prefix_refs if r not in all_old_chks]
1610 self._old_queue.extend(refs)1670 self._old_queue.extend(refs)
@@ -1660,3 +1720,22 @@
1660 )1720 )
1661search_key_registry.register('hash-16-way', _search_key_16)1721search_key_registry.register('hash-16-way', _search_key_16)
1662search_key_registry.register('hash-255-way', _search_key_255)1722search_key_registry.register('hash-255-way', _search_key_255)
1723
1724
1725def _check_key(key):
1726 """Helper function to assert that a key is properly formatted.
1727
1728 This generally shouldn't be used in production code, but it can be helpful
1729 to debug problems.
1730 """
1731 if type(key) is not StaticTuple:
1732 raise TypeError('key %r is not StaticTuple but %s' % (key, type(key)))
1733 if len(key) != 1:
1734 raise ValueError('key %r should have length 1, not %d' % (key, len(key),))
1735 if type(key[0]) is not str:
1736 raise TypeError('key %r should hold a str, not %r'
1737 % (key, type(key[0])))
1738 if not key[0].startswith('sha1:'):
1739 raise ValueError('key %r should point to a sha1:' % (key,))
1740
1741
16631742
=== modified file 'bzrlib/groupcompress.py'
--- bzrlib/groupcompress.py 2009-10-19 15:45:10 +0000
+++ bzrlib/groupcompress.py 2009-10-26 14:59:15 +0000
@@ -1269,6 +1269,7 @@
1269 """See VersionedFiles.clear_cache()"""1269 """See VersionedFiles.clear_cache()"""
1270 self._group_cache.clear()1270 self._group_cache.clear()
1271 self._index._graph_index.clear_cache()1271 self._index._graph_index.clear_cache()
1272 self._index._int_cache.clear()
12721273
1273 def _check_add(self, key, lines, random_id, check_content):1274 def _check_add(self, key, lines, random_id, check_content):
1274 """check that version_id and lines are safe to add."""1275 """check that version_id and lines are safe to add."""
@@ -1832,6 +1833,9 @@
1832 self.has_graph = parents1833 self.has_graph = parents
1833 self._is_locked = is_locked1834 self._is_locked = is_locked
1834 self._inconsistency_fatal = inconsistency_fatal1835 self._inconsistency_fatal = inconsistency_fatal
1836 # GroupCompress records tend to have the same 'group' start + offset
1837 # repeated over and over, this creates a surplus of ints
1838 self._int_cache = {}
1835 if track_external_parent_refs:1839 if track_external_parent_refs:
1836 self._key_dependencies = knit._KeyRefs(1840 self._key_dependencies = knit._KeyRefs(
1837 track_new_keys=track_new_keys)1841 track_new_keys=track_new_keys)
@@ -2013,11 +2017,24 @@
2013 """Convert an index value to position details."""2017 """Convert an index value to position details."""
2014 bits = node[2].split(' ')2018 bits = node[2].split(' ')
2015 # It would be nice not to read the entire gzip.2019 # It would be nice not to read the entire gzip.
2020 # start and stop are put into _int_cache because they are very common.
2021 # They define the 'group' that an entry is in, and many groups can have
2022 # thousands of objects.
2023 # Branching Launchpad, for example, saves ~600k integers, at 12 bytes
2024 # each, or about 7MB. Note that it might be even more when you consider
2025 # how PyInt is allocated in separate slabs. And you can't return a slab
2026 # to the OS if even 1 int on it is in use. Note though that Python uses
2027 # a LIFO when re-using PyInt slots, which probably causes more
2028 # fragmentation.
2016 start = int(bits[0])2029 start = int(bits[0])
2030 start = self._int_cache.setdefault(start, start)
2017 stop = int(bits[1])2031 stop = int(bits[1])
2032 stop = self._int_cache.setdefault(stop, stop)
2018 basis_end = int(bits[2])2033 basis_end = int(bits[2])
2019 delta_end = int(bits[3])2034 delta_end = int(bits[3])
2020 return node[0], start, stop, basis_end, delta_end2035 # We can't use StaticTuple here, because node[0] is a BTreeGraphIndex
2036 # instance...
2037 return (node[0], start, stop, basis_end, delta_end)
20212038
2022 def scan_unvalidated_index(self, graph_index):2039 def scan_unvalidated_index(self, graph_index):
2023 """Inform this _GCGraphIndex that there is an unvalidated index.2040 """Inform this _GCGraphIndex that there is an unvalidated index.
20242041
=== modified file 'bzrlib/inventory.py'
--- bzrlib/inventory.py 2009-10-20 20:29:11 +0000
+++ bzrlib/inventory.py 2009-10-26 14:59:15 +0000
@@ -51,6 +51,7 @@
51 )51 )
52from bzrlib.symbol_versioning import deprecated_in, deprecated_method52from bzrlib.symbol_versioning import deprecated_in, deprecated_method
53from bzrlib.trace import mutter53from bzrlib.trace import mutter
54from bzrlib.static_tuple import StaticTuple
5455
5556
56class InventoryEntry(object):57class InventoryEntry(object):
@@ -1599,8 +1600,6 @@
1599 interesting.add(None) # this will auto-filter it in the loop1600 interesting.add(None) # this will auto-filter it in the loop
1600 remaining_parents.discard(None) 1601 remaining_parents.discard(None)
1601 while remaining_parents:1602 while remaining_parents:
1602 if None in remaining_parents:
1603 import pdb; pdb.set_trace()
1604 next_parents = set()1603 next_parents = set()
1605 for entry in self._getitems(remaining_parents):1604 for entry in self._getitems(remaining_parents):
1606 next_parents.add(entry.parent_id)1605 next_parents.add(entry.parent_id)
@@ -1615,7 +1614,7 @@
1615 while directories_to_expand:1614 while directories_to_expand:
1616 # Expand directories by looking in the1615 # Expand directories by looking in the
1617 # parent_id_basename_to_file_id map1616 # parent_id_basename_to_file_id map
1618 keys = [(f,) for f in directories_to_expand]1617 keys = [StaticTuple(f,).intern() for f in directories_to_expand]
1619 directories_to_expand = set()1618 directories_to_expand = set()
1620 items = self.parent_id_basename_to_file_id.iteritems(keys)1619 items = self.parent_id_basename_to_file_id.iteritems(keys)
1621 next_file_ids = set([item[1] for item in items])1620 next_file_ids = set([item[1] for item in items])
@@ -1810,7 +1809,7 @@
1810 pass1809 pass
1811 deletes.add(file_id)1810 deletes.add(file_id)
1812 else:1811 else:
1813 new_key = (file_id,)1812 new_key = StaticTuple(file_id,)
1814 new_value = result._entry_to_bytes(entry)1813 new_value = result._entry_to_bytes(entry)
1815 # Update caches. It's worth doing this whether1814 # Update caches. It's worth doing this whether
1816 # we're propagating the old caches or not.1815 # we're propagating the old caches or not.
@@ -1819,13 +1818,13 @@
1819 if old_path is None:1818 if old_path is None:
1820 old_key = None1819 old_key = None
1821 else:1820 else:
1822 old_key = (file_id,)1821 old_key = StaticTuple(file_id,)
1823 if self.id2path(file_id) != old_path:1822 if self.id2path(file_id) != old_path:
1824 raise errors.InconsistentDelta(old_path, file_id,1823 raise errors.InconsistentDelta(old_path, file_id,
1825 "Entry was at wrong other path %r." %1824 "Entry was at wrong other path %r." %
1826 self.id2path(file_id))1825 self.id2path(file_id))
1827 altered.add(file_id)1826 altered.add(file_id)
1828 id_to_entry_delta.append((old_key, new_key, new_value))1827 id_to_entry_delta.append(StaticTuple(old_key, new_key, new_value))
1829 if result.parent_id_basename_to_file_id is not None:1828 if result.parent_id_basename_to_file_id is not None:
1830 # parent_id, basename changes1829 # parent_id, basename changes
1831 if old_path is None:1830 if old_path is None:
@@ -1923,7 +1922,13 @@
1923 search_key_name = intern(info.get('search_key_name', 'plain'))1922 search_key_name = intern(info.get('search_key_name', 'plain'))
1924 parent_id_basename_to_file_id = intern(info.get(1923 parent_id_basename_to_file_id = intern(info.get(
1925 'parent_id_basename_to_file_id', None))1924 'parent_id_basename_to_file_id', None))
1925 if not parent_id_basename_to_file_id.startswith('sha1:'):
1926 raise ValueError('parent_id_basename_to_file_id should be a sha1'
1927 ' key not %r' % (parent_id_basename_to_file_id,))
1926 id_to_entry = info['id_to_entry']1928 id_to_entry = info['id_to_entry']
1929 if not id_to_entry.startswith('sha1:'):
1930 raise ValueError('id_to_entry should be a sha1'
1931 ' key not %r' % (id_to_entry,))
19271932
1928 result = CHKInventory(search_key_name)1933 result = CHKInventory(search_key_name)
1929 result.revision_id = revision_id1934 result.revision_id = revision_id
@@ -1932,12 +1937,13 @@
1932 result._search_key_name)1937 result._search_key_name)
1933 if parent_id_basename_to_file_id is not None:1938 if parent_id_basename_to_file_id is not None:
1934 result.parent_id_basename_to_file_id = chk_map.CHKMap(1939 result.parent_id_basename_to_file_id = chk_map.CHKMap(
1935 chk_store, (parent_id_basename_to_file_id,),1940 chk_store, StaticTuple(parent_id_basename_to_file_id,),
1936 search_key_func=search_key_func)1941 search_key_func=search_key_func)
1937 else:1942 else:
1938 result.parent_id_basename_to_file_id = None1943 result.parent_id_basename_to_file_id = None
19391944
1940 result.id_to_entry = chk_map.CHKMap(chk_store, (id_to_entry,),1945 result.id_to_entry = chk_map.CHKMap(chk_store,
1946 StaticTuple(id_to_entry,),
1941 search_key_func=search_key_func)1947 search_key_func=search_key_func)
1942 if (result.revision_id,) != expected_revision_id:1948 if (result.revision_id,) != expected_revision_id:
1943 raise ValueError("Mismatched revision id and expected: %r, %r" %1949 raise ValueError("Mismatched revision id and expected: %r, %r" %
@@ -1965,7 +1971,8 @@
1965 id_to_entry_dict = {}1971 id_to_entry_dict = {}
1966 parent_id_basename_dict = {}1972 parent_id_basename_dict = {}
1967 for path, entry in inventory.iter_entries():1973 for path, entry in inventory.iter_entries():
1968 id_to_entry_dict[(entry.file_id,)] = entry_to_bytes(entry)1974 key = StaticTuple(entry.file_id,).intern()
1975 id_to_entry_dict[key] = entry_to_bytes(entry)
1969 p_id_key = parent_id_basename_key(entry)1976 p_id_key = parent_id_basename_key(entry)
1970 parent_id_basename_dict[p_id_key] = entry.file_id1977 parent_id_basename_dict[p_id_key] = entry.file_id
19711978
@@ -1994,7 +2001,7 @@
1994 parent_id = entry.parent_id2001 parent_id = entry.parent_id
1995 else:2002 else:
1996 parent_id = ''2003 parent_id = ''
1997 return parent_id, entry.name.encode('utf8')2004 return StaticTuple(parent_id, entry.name.encode('utf8')).intern()
19982005
1999 def __getitem__(self, file_id):2006 def __getitem__(self, file_id):
2000 """map a single file_id -> InventoryEntry."""2007 """map a single file_id -> InventoryEntry."""
@@ -2005,7 +2012,7 @@
2005 return result2012 return result
2006 try:2013 try:
2007 return self._bytes_to_entry(2014 return self._bytes_to_entry(
2008 self.id_to_entry.iteritems([(file_id,)]).next()[1])2015 self.id_to_entry.iteritems([StaticTuple(file_id,)]).next()[1])
2009 except StopIteration:2016 except StopIteration:
2010 # really we're passing an inventory, not a tree...2017 # really we're passing an inventory, not a tree...
2011 raise errors.NoSuchId(self, file_id)2018 raise errors.NoSuchId(self, file_id)
@@ -2024,7 +2031,7 @@
2024 remaining.append(file_id)2031 remaining.append(file_id)
2025 else:2032 else:
2026 result.append(entry)2033 result.append(entry)
2027 file_keys = [(f,) for f in remaining]2034 file_keys = [StaticTuple(f,).intern() for f in remaining]
2028 for file_key, value in self.id_to_entry.iteritems(file_keys):2035 for file_key, value in self.id_to_entry.iteritems(file_keys):
2029 entry = self._bytes_to_entry(value)2036 entry = self._bytes_to_entry(value)
2030 result.append(entry)2037 result.append(entry)
@@ -2035,7 +2042,8 @@
2035 # Perhaps have an explicit 'contains' method on CHKMap ?2042 # Perhaps have an explicit 'contains' method on CHKMap ?
2036 if self._fileid_to_entry_cache.get(file_id, None) is not None:2043 if self._fileid_to_entry_cache.get(file_id, None) is not None:
2037 return True2044 return True
2038 return len(list(self.id_to_entry.iteritems([(file_id,)]))) == 12045 return len(list(
2046 self.id_to_entry.iteritems([StaticTuple(file_id,)]))) == 1
20392047
2040 def is_root(self, file_id):2048 def is_root(self, file_id):
2041 return file_id == self.root_id2049 return file_id == self.root_id
@@ -2193,7 +2201,7 @@
2193 basename_utf8 = basename.encode('utf8')2201 basename_utf8 = basename.encode('utf8')
2194 file_id = self._path_to_fileid_cache.get(cur_path, None)2202 file_id = self._path_to_fileid_cache.get(cur_path, None)
2195 if file_id is None:2203 if file_id is None:
2196 key_filter = [(current_id, basename_utf8)]2204 key_filter = [StaticTuple(current_id, basename_utf8)]
2197 items = parent_id_index.iteritems(key_filter)2205 items = parent_id_index.iteritems(key_filter)
2198 for (parent_id, name_utf8), file_id in items:2206 for (parent_id, name_utf8), file_id in items:
2199 if parent_id != current_id or name_utf8 != basename_utf8:2207 if parent_id != current_id or name_utf8 != basename_utf8:
@@ -2215,16 +2223,16 @@
2215 lines.append('search_key_name: %s\n' % (self._search_key_name,))2223 lines.append('search_key_name: %s\n' % (self._search_key_name,))
2216 lines.append("root_id: %s\n" % self.root_id)2224 lines.append("root_id: %s\n" % self.root_id)
2217 lines.append('parent_id_basename_to_file_id: %s\n' %2225 lines.append('parent_id_basename_to_file_id: %s\n' %
2218 self.parent_id_basename_to_file_id.key())2226 (self.parent_id_basename_to_file_id.key()[0],))
2219 lines.append("revision_id: %s\n" % self.revision_id)2227 lines.append("revision_id: %s\n" % self.revision_id)
2220 lines.append("id_to_entry: %s\n" % self.id_to_entry.key())2228 lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2221 else:2229 else:
2222 lines.append("revision_id: %s\n" % self.revision_id)2230 lines.append("revision_id: %s\n" % self.revision_id)
2223 lines.append("root_id: %s\n" % self.root_id)2231 lines.append("root_id: %s\n" % self.root_id)
2224 if self.parent_id_basename_to_file_id is not None:2232 if self.parent_id_basename_to_file_id is not None:
2225 lines.append('parent_id_basename_to_file_id: %s\n' %2233 lines.append('parent_id_basename_to_file_id: %s\n' %
2226 self.parent_id_basename_to_file_id.key())2234 (self.parent_id_basename_to_file_id.key()[0],))
2227 lines.append("id_to_entry: %s\n" % self.id_to_entry.key())2235 lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2228 return lines2236 return lines
22292237
2230 @property2238 @property
@@ -2271,8 +2279,8 @@
2271 parent_id_index = self._chk_inventory.parent_id_basename_to_file_id2279 parent_id_index = self._chk_inventory.parent_id_basename_to_file_id
2272 child_keys = set()2280 child_keys = set()
2273 for (parent_id, name_utf8), file_id in parent_id_index.iteritems(2281 for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
2274 key_filter=[(self.file_id,)]):2282 key_filter=[StaticTuple(self.file_id,)]):
2275 child_keys.add((file_id,))2283 child_keys.add(StaticTuple(file_id,))
2276 cached = set()2284 cached = set()
2277 for file_id_key in child_keys:2285 for file_id_key in child_keys:
2278 entry = self._chk_inventory._fileid_to_entry_cache.get(2286 entry = self._chk_inventory._fileid_to_entry_cache.get(
22792287
=== modified file 'bzrlib/repofmt/groupcompress_repo.py'
--- bzrlib/repofmt/groupcompress_repo.py 2009-10-19 16:21:20 +0000
+++ bzrlib/repofmt/groupcompress_repo.py 2009-10-26 14:59:15 +0000
@@ -53,6 +53,7 @@
53 ResumedPack,53 ResumedPack,
54 Packer,54 Packer,
55 )55 )
56from bzrlib.static_tuple import StaticTuple
5657
5758
58class GCPack(NewPack):59class GCPack(NewPack):
@@ -814,14 +815,16 @@
814 ' no new_path %r' % (file_id,))815 ' no new_path %r' % (file_id,))
815 if new_path == '':816 if new_path == '':
816 new_inv.root_id = file_id817 new_inv.root_id = file_id
817 parent_id_basename_key = ('', '')818 parent_id_basename_key = StaticTuple('', '').intern()
818 else:819 else:
819 utf8_entry_name = entry.name.encode('utf-8')820 utf8_entry_name = entry.name.encode('utf-8')
820 parent_id_basename_key = (entry.parent_id, utf8_entry_name)821 parent_id_basename_key = StaticTuple(entry.parent_id,
822 utf8_entry_name).intern()
821 new_value = entry_to_bytes(entry)823 new_value = entry_to_bytes(entry)
822 # Populate Caches?824 # Populate Caches?
823 # new_inv._path_to_fileid_cache[new_path] = file_id825 # new_inv._path_to_fileid_cache[new_path] = file_id
824 id_to_entry_dict[(file_id,)] = new_value826 key = StaticTuple(file_id).intern()
827 id_to_entry_dict[key] = new_value
825 parent_id_basename_dict[parent_id_basename_key] = file_id828 parent_id_basename_dict[parent_id_basename_key] = file_id
826829
827 new_inv._populate_from_dicts(self.chk_bytes, id_to_entry_dict,830 new_inv._populate_from_dicts(self.chk_bytes, id_to_entry_dict,
@@ -949,6 +952,10 @@
949 pb=pb):952 pb=pb):
950 for name, bytes in items:953 for name, bytes in items:
951 (name_utf8, file_id, revision_id) = bytes_to_info(bytes)954 (name_utf8, file_id, revision_id) = bytes_to_info(bytes)
955 # TODO: consider interning file_id, revision_id here, or
956 # pushing that intern() into bytes_to_info()
957 # TODO: rich_root should always be True here, for all
958 # repositories that support chk_bytes
952 if not rich_root and name_utf8 == '':959 if not rich_root and name_utf8 == '':
953 continue960 continue
954 try:961 try:
@@ -1189,7 +1196,9 @@
1189 # are always rich-root, so there are no synthesised root records to1196 # are always rich-root, so there are no synthesised root records to
1190 # ignore.1197 # ignore.
1191 _, file_id, revision_id = bytes_to_info(bytes)1198 _, file_id, revision_id = bytes_to_info(bytes)
1192 text_keys.add((file_id, revision_id))1199 file_id = intern(file_id)
1200 revision_id = intern(revision_id)
1201 text_keys.add(StaticTuple(file_id, revision_id).intern())
1193 yield record1202 yield record
11941203
11951204
11961205
=== modified file 'bzrlib/tests/test__chk_map.py'
--- bzrlib/tests/test__chk_map.py 2009-04-09 20:23:07 +0000
+++ bzrlib/tests/test__chk_map.py 2009-10-26 14:59:15 +0000
@@ -20,6 +20,8 @@
20 chk_map,20 chk_map,
21 tests,21 tests,
22 )22 )
23from bzrlib.static_tuple import StaticTuple
24stuple = StaticTuple
2325
2426
25def load_tests(standard_tests, module, loader):27def load_tests(standard_tests, module, loader):
@@ -67,25 +69,25 @@
67 self.assertEqual(expected, actual, 'actual: %r' % (actual,))69 self.assertEqual(expected, actual, 'actual: %r' % (actual,))
6870
69 def test_simple_16(self):71 def test_simple_16(self):
70 self.assertSearchKey16('8C736521', ('foo',))72 self.assertSearchKey16('8C736521', stuple('foo',))
71 self.assertSearchKey16('8C736521\x008C736521', ('foo', 'foo'))73 self.assertSearchKey16('8C736521\x008C736521', stuple('foo', 'foo'))
72 self.assertSearchKey16('8C736521\x0076FF8CAA', ('foo', 'bar'))74 self.assertSearchKey16('8C736521\x0076FF8CAA', stuple('foo', 'bar'))
73 self.assertSearchKey16('ED82CD11', ('abcd',))75 self.assertSearchKey16('ED82CD11', stuple('abcd',))
7476
75 def test_simple_255(self):77 def test_simple_255(self):
76 self.assertSearchKey255('\x8cse!', ('foo',))78 self.assertSearchKey255('\x8cse!', stuple('foo',))
77 self.assertSearchKey255('\x8cse!\x00\x8cse!', ('foo', 'foo'))79 self.assertSearchKey255('\x8cse!\x00\x8cse!', stuple('foo', 'foo'))
78 self.assertSearchKey255('\x8cse!\x00v\xff\x8c\xaa', ('foo', 'bar'))80 self.assertSearchKey255('\x8cse!\x00v\xff\x8c\xaa', stuple('foo', 'bar'))
79 # The standard mapping for these would include '\n', so it should be81 # The standard mapping for these would include '\n', so it should be
80 # mapped to '_'82 # mapped to '_'
81 self.assertSearchKey255('\xfdm\x93_\x00P_\x1bL', ('<', 'V'))83 self.assertSearchKey255('\xfdm\x93_\x00P_\x1bL', stuple('<', 'V'))
8284
83 def test_255_does_not_include_newline(self):85 def test_255_does_not_include_newline(self):
84 # When mapping via _search_key_255, we should never have the '\n'86 # When mapping via _search_key_255, we should never have the '\n'
85 # character, but all other 255 values should be present87 # character, but all other 255 values should be present
86 chars_used = set()88 chars_used = set()
87 for char_in in range(256):89 for char_in in range(256):
88 search_key = self.module._search_key_255((chr(char_in),))90 search_key = self.module._search_key_255(stuple(chr(char_in),))
89 chars_used.update(search_key)91 chars_used.update(search_key)
90 all_chars = set([chr(x) for x in range(256)])92 all_chars = set([chr(x) for x in range(256)])
91 unused_chars = all_chars.symmetric_difference(chars_used)93 unused_chars = all_chars.symmetric_difference(chars_used)
@@ -113,10 +115,11 @@
113115
114 def test_deserialise_empty(self):116 def test_deserialise_empty(self):
115 node = self.module._deserialise_leaf_node(117 node = self.module._deserialise_leaf_node(
116 "chkleaf:\n10\n1\n0\n\n", ("sha1:1234",))118 "chkleaf:\n10\n1\n0\n\n", stuple("sha1:1234",))
117 self.assertEqual(0, len(node))119 self.assertEqual(0, len(node))
118 self.assertEqual(10, node.maximum_size)120 self.assertEqual(10, node.maximum_size)
119 self.assertEqual(("sha1:1234",), node.key())121 self.assertEqual(("sha1:1234",), node.key())
122 self.assertIsInstance(node.key(), StaticTuple)
120 self.assertIs(None, node._search_prefix)123 self.assertIs(None, node._search_prefix)
121 self.assertIs(None, node._common_serialised_prefix)124 self.assertIs(None, node._common_serialised_prefix)
122125
@@ -194,7 +197,8 @@
194197
195 def assertDeserialiseErrors(self, text):198 def assertDeserialiseErrors(self, text):
196 self.assertRaises((ValueError, IndexError),199 self.assertRaises((ValueError, IndexError),
197 self.module._deserialise_internal_node, text, 'not-a-real-sha')200 self.module._deserialise_internal_node, text,
201 stuple('not-a-real-sha',))
198202
199 def test_raises_on_non_internal(self):203 def test_raises_on_non_internal(self):
200 self.assertDeserialiseErrors('')204 self.assertDeserialiseErrors('')
@@ -211,7 +215,7 @@
211215
212 def test_deserialise_one(self):216 def test_deserialise_one(self):
213 node = self.module._deserialise_internal_node(217 node = self.module._deserialise_internal_node(
214 "chknode:\n10\n1\n1\n\na\x00sha1:abcd\n", ('sha1:1234',))218 "chknode:\n10\n1\n1\n\na\x00sha1:abcd\n", stuple('sha1:1234',))
215 self.assertIsInstance(node, chk_map.InternalNode)219 self.assertIsInstance(node, chk_map.InternalNode)
216 self.assertEqual(1, len(node))220 self.assertEqual(1, len(node))
217 self.assertEqual(10, node.maximum_size)221 self.assertEqual(10, node.maximum_size)
@@ -221,7 +225,7 @@
221225
222 def test_deserialise_with_prefix(self):226 def test_deserialise_with_prefix(self):
223 node = self.module._deserialise_internal_node(227 node = self.module._deserialise_internal_node(
224 "chknode:\n10\n1\n1\npref\na\x00sha1:abcd\n", ('sha1:1234',))228 "chknode:\n10\n1\n1\npref\na\x00sha1:abcd\n", stuple('sha1:1234',))
225 self.assertIsInstance(node, chk_map.InternalNode)229 self.assertIsInstance(node, chk_map.InternalNode)
226 self.assertEqual(1, len(node))230 self.assertEqual(1, len(node))
227 self.assertEqual(10, node.maximum_size)231 self.assertEqual(10, node.maximum_size)
@@ -230,7 +234,7 @@
230 self.assertEqual({'prefa': ('sha1:abcd',)}, node._items)234 self.assertEqual({'prefa': ('sha1:abcd',)}, node._items)
231235
232 node = self.module._deserialise_internal_node(236 node = self.module._deserialise_internal_node(
233 "chknode:\n10\n1\n1\npref\n\x00sha1:abcd\n", ('sha1:1234',))237 "chknode:\n10\n1\n1\npref\n\x00sha1:abcd\n", stuple('sha1:1234',))
234 self.assertIsInstance(node, chk_map.InternalNode)238 self.assertIsInstance(node, chk_map.InternalNode)
235 self.assertEqual(1, len(node))239 self.assertEqual(1, len(node))
236 self.assertEqual(10, node.maximum_size)240 self.assertEqual(10, node.maximum_size)
@@ -240,7 +244,8 @@
240244
241 def test_deserialise_pref_with_null(self):245 def test_deserialise_pref_with_null(self):
242 node = self.module._deserialise_internal_node(246 node = self.module._deserialise_internal_node(
243 "chknode:\n10\n1\n1\npref\x00fo\n\x00sha1:abcd\n", ('sha1:1234',))247 "chknode:\n10\n1\n1\npref\x00fo\n\x00sha1:abcd\n",
248 stuple('sha1:1234',))
244 self.assertIsInstance(node, chk_map.InternalNode)249 self.assertIsInstance(node, chk_map.InternalNode)
245 self.assertEqual(1, len(node))250 self.assertEqual(1, len(node))
246 self.assertEqual(10, node.maximum_size)251 self.assertEqual(10, node.maximum_size)
@@ -250,7 +255,8 @@
250255
251 def test_deserialise_with_null_pref(self):256 def test_deserialise_with_null_pref(self):
252 node = self.module._deserialise_internal_node(257 node = self.module._deserialise_internal_node(
253 "chknode:\n10\n1\n1\npref\x00fo\n\x00\x00sha1:abcd\n", ('sha1:1234',))258 "chknode:\n10\n1\n1\npref\x00fo\n\x00\x00sha1:abcd\n",
259 stuple('sha1:1234',))
254 self.assertIsInstance(node, chk_map.InternalNode)260 self.assertIsInstance(node, chk_map.InternalNode)
255 self.assertEqual(1, len(node))261 self.assertEqual(1, len(node))
256 self.assertEqual(10, node.maximum_size)262 self.assertEqual(10, node.maximum_size)
257263
=== modified file 'bzrlib/tests/test_chk_map.py'
--- bzrlib/tests/test_chk_map.py 2009-10-08 04:35:01 +0000
+++ bzrlib/tests/test_chk_map.py 2009-10-26 14:59:15 +0000
@@ -31,6 +31,7 @@
31 LeafNode,31 LeafNode,
32 Node,32 Node,
33 )33 )
34from bzrlib.static_tuple import StaticTuple
3435
3536
36class TestNode(tests.TestCase):37class TestNode(tests.TestCase):
@@ -831,13 +832,13 @@
831 # 'ab' and 'ac' nodes832 # 'ab' and 'ac' nodes
832 chkmap.map(('aad',), 'v')833 chkmap.map(('aad',), 'v')
833 self.assertIsInstance(chkmap._root_node._items['aa'], InternalNode)834 self.assertIsInstance(chkmap._root_node._items['aa'], InternalNode)
834 self.assertIsInstance(chkmap._root_node._items['ab'], tuple)835 self.assertIsInstance(chkmap._root_node._items['ab'], StaticTuple)
835 self.assertIsInstance(chkmap._root_node._items['ac'], tuple)836 self.assertIsInstance(chkmap._root_node._items['ac'], StaticTuple)
836 # Unmapping 'acd' can notice that 'aa' is an InternalNode and not have837 # Unmapping 'acd' can notice that 'aa' is an InternalNode and not have
837 # to map in 'ab'838 # to map in 'ab'
838 chkmap.unmap(('acd',))839 chkmap.unmap(('acd',))
839 self.assertIsInstance(chkmap._root_node._items['aa'], InternalNode)840 self.assertIsInstance(chkmap._root_node._items['aa'], InternalNode)
840 self.assertIsInstance(chkmap._root_node._items['ab'], tuple)841 self.assertIsInstance(chkmap._root_node._items['ab'], StaticTuple)
841842
842 def test_unmap_without_fitting_doesnt_page_in(self):843 def test_unmap_without_fitting_doesnt_page_in(self):
843 store = self.get_chk_bytes()844 store = self.get_chk_bytes()
@@ -860,8 +861,8 @@
860 chkmap.map(('aaf',), 'v')861 chkmap.map(('aaf',), 'v')
861 # At this point, the previous nodes should not be paged in, but the862 # At this point, the previous nodes should not be paged in, but the
862 # newly added nodes would be863 # newly added nodes would be
863 self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)864 self.assertIsInstance(chkmap._root_node._items['aaa'], StaticTuple)
864 self.assertIsInstance(chkmap._root_node._items['aab'], tuple)865 self.assertIsInstance(chkmap._root_node._items['aab'], StaticTuple)
865 self.assertIsInstance(chkmap._root_node._items['aac'], LeafNode)866 self.assertIsInstance(chkmap._root_node._items['aac'], LeafNode)
866 self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)867 self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)
867 self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)868 self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)
@@ -869,8 +870,8 @@
869 # Now unmapping one of the new nodes will use only the already-paged-in870 # Now unmapping one of the new nodes will use only the already-paged-in
870 # nodes to determine that we don't need to do more.871 # nodes to determine that we don't need to do more.
871 chkmap.unmap(('aaf',))872 chkmap.unmap(('aaf',))
872 self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)873 self.assertIsInstance(chkmap._root_node._items['aaa'], StaticTuple)
873 self.assertIsInstance(chkmap._root_node._items['aab'], tuple)874 self.assertIsInstance(chkmap._root_node._items['aab'], StaticTuple)
874 self.assertIsInstance(chkmap._root_node._items['aac'], LeafNode)875 self.assertIsInstance(chkmap._root_node._items['aac'], LeafNode)
875 self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)876 self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)
876 self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)877 self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)
@@ -897,9 +898,9 @@
897 chkmap.map(('aad',), 'v')898 chkmap.map(('aad',), 'v')
898 # At this point, the previous nodes should not be paged in, but the899 # At this point, the previous nodes should not be paged in, but the
899 # newly added node would be900 # newly added node would be
900 self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)901 self.assertIsInstance(chkmap._root_node._items['aaa'], StaticTuple)
901 self.assertIsInstance(chkmap._root_node._items['aab'], tuple)902 self.assertIsInstance(chkmap._root_node._items['aab'], StaticTuple)
902 self.assertIsInstance(chkmap._root_node._items['aac'], tuple)903 self.assertIsInstance(chkmap._root_node._items['aac'], StaticTuple)
903 self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)904 self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)
904 # Unmapping the new node will check the existing nodes to see if they905 # Unmapping the new node will check the existing nodes to see if they
905 # would fit.906 # would fit.
@@ -937,9 +938,9 @@
937 chkmap.map(('aad',), 'v')938 chkmap.map(('aad',), 'v')
938 # At this point, the previous nodes should not be paged in, but the939 # At this point, the previous nodes should not be paged in, but the
939 # newly added node would be940 # newly added node would be
940 self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)941 self.assertIsInstance(chkmap._root_node._items['aaa'], StaticTuple)
941 self.assertIsInstance(chkmap._root_node._items['aab'], tuple)942 self.assertIsInstance(chkmap._root_node._items['aab'], StaticTuple)
942 self.assertIsInstance(chkmap._root_node._items['aac'], tuple)943 self.assertIsInstance(chkmap._root_node._items['aac'], StaticTuple)
943 self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)944 self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)
944 # Now clear the page cache, and only include 2 of the children in the945 # Now clear the page cache, and only include 2 of the children in the
945 # cache946 # cache
@@ -954,7 +955,7 @@
954 # Unmapping the new node will check the nodes from the page cache955 # Unmapping the new node will check the nodes from the page cache
955 # first, and not have to read in 'aaa'956 # first, and not have to read in 'aaa'
956 chkmap.unmap(('aad',))957 chkmap.unmap(('aad',))
957 self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)958 self.assertIsInstance(chkmap._root_node._items['aaa'], StaticTuple)
958 self.assertIsInstance(chkmap._root_node._items['aab'], LeafNode)959 self.assertIsInstance(chkmap._root_node._items['aab'], LeafNode)
959 self.assertIsInstance(chkmap._root_node._items['aac'], LeafNode)960 self.assertIsInstance(chkmap._root_node._items['aac'], LeafNode)
960961
@@ -974,9 +975,9 @@
974 chkmap.map(('aaf',), 'val')975 chkmap.map(('aaf',), 'val')
975 # At this point, the previous nodes should not be paged in, but the976 # At this point, the previous nodes should not be paged in, but the
976 # newly added node would be977 # newly added node would be
977 self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)978 self.assertIsInstance(chkmap._root_node._items['aaa'], StaticTuple)
978 self.assertIsInstance(chkmap._root_node._items['aab'], tuple)979 self.assertIsInstance(chkmap._root_node._items['aab'], StaticTuple)
979 self.assertIsInstance(chkmap._root_node._items['aac'], tuple)980 self.assertIsInstance(chkmap._root_node._items['aac'], StaticTuple)
980 self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)981 self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)
981 self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)982 self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)
982 self.assertIsInstance(chkmap._root_node._items['aaf'], LeafNode)983 self.assertIsInstance(chkmap._root_node._items['aaf'], LeafNode)
@@ -984,9 +985,9 @@
984 # Unmapping a new node will see the other nodes that are already in985 # Unmapping a new node will see the other nodes that are already in
985 # memory, and not need to page in anything else986 # memory, and not need to page in anything else
986 chkmap.unmap(('aad',))987 chkmap.unmap(('aad',))
987 self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)988 self.assertIsInstance(chkmap._root_node._items['aaa'], StaticTuple)
988 self.assertIsInstance(chkmap._root_node._items['aab'], tuple)989 self.assertIsInstance(chkmap._root_node._items['aab'], StaticTuple)
989 self.assertIsInstance(chkmap._root_node._items['aac'], tuple)990 self.assertIsInstance(chkmap._root_node._items['aac'], StaticTuple)
990 self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)991 self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)
991 self.assertIsInstance(chkmap._root_node._items['aaf'], LeafNode)992 self.assertIsInstance(chkmap._root_node._items['aaf'], LeafNode)
992993
@@ -1031,8 +1032,8 @@
1031 {('a',): 'content here', ('b',): 'more content'},1032 {('a',): 'content here', ('b',): 'more content'},
1032 chk_bytes=basis._store, maximum_size=10)1033 chk_bytes=basis._store, maximum_size=10)
1033 list(target.iter_changes(basis))1034 list(target.iter_changes(basis))
1034 self.assertIsInstance(target._root_node, tuple)1035 self.assertIsInstance(target._root_node, StaticTuple)
1035 self.assertIsInstance(basis._root_node, tuple)1036 self.assertIsInstance(basis._root_node, StaticTuple)
10361037
1037 def test_iter_changes_ab_ab_changed_values_shown(self):1038 def test_iter_changes_ab_ab_changed_values_shown(self):
1038 basis = self._get_map({('a',): 'content here', ('b',): 'more content'},1039 basis = self._get_map({('a',): 'content here', ('b',): 'more content'},
@@ -1144,9 +1145,12 @@
11441145
1145 def test_iteritems_keys_prefixed_by_2_width_nodes_hashed(self):1146 def test_iteritems_keys_prefixed_by_2_width_nodes_hashed(self):
1146 search_key_func = chk_map.search_key_registry.get('hash-16-way')1147 search_key_func = chk_map.search_key_registry.get('hash-16-way')
1147 self.assertEqual('E8B7BE43\x00E8B7BE43', search_key_func(('a', 'a')))1148 self.assertEqual('E8B7BE43\x00E8B7BE43',
1148 self.assertEqual('E8B7BE43\x0071BEEFF9', search_key_func(('a', 'b')))1149 search_key_func(StaticTuple('a', 'a')))
1149 self.assertEqual('71BEEFF9\x0000000000', search_key_func(('b', '')))1150 self.assertEqual('E8B7BE43\x0071BEEFF9',
1151 search_key_func(StaticTuple('a', 'b')))
1152 self.assertEqual('71BEEFF9\x0000000000',
1153 search_key_func(StaticTuple('b', '')))
1150 chkmap = self._get_map(1154 chkmap = self._get_map(
1151 {("a","a"):"content here", ("a", "b",):"more content",1155 {("a","a"):"content here", ("a", "b",):"more content",
1152 ("b", ""): 'boring content'},1156 ("b", ""): 'boring content'},
@@ -1449,41 +1453,6 @@
1449 , chkmap._dump_tree())1453 , chkmap._dump_tree())
14501454
14511455
1452class TestSearchKeyFuncs(tests.TestCase):
1453
1454 def assertSearchKey16(self, expected, key):
1455 self.assertEqual(expected, chk_map._search_key_16(key))
1456
1457 def assertSearchKey255(self, expected, key):
1458 actual = chk_map._search_key_255(key)
1459 self.assertEqual(expected, actual, 'actual: %r' % (actual,))
1460
1461 def test_simple_16(self):
1462 self.assertSearchKey16('8C736521', ('foo',))
1463 self.assertSearchKey16('8C736521\x008C736521', ('foo', 'foo'))
1464 self.assertSearchKey16('8C736521\x0076FF8CAA', ('foo', 'bar'))
1465 self.assertSearchKey16('ED82CD11', ('abcd',))
1466
1467 def test_simple_255(self):
1468 self.assertSearchKey255('\x8cse!', ('foo',))
1469 self.assertSearchKey255('\x8cse!\x00\x8cse!', ('foo', 'foo'))
1470 self.assertSearchKey255('\x8cse!\x00v\xff\x8c\xaa', ('foo', 'bar'))
1471 # The standard mapping for these would include '\n', so it should be
1472 # mapped to '_'
1473 self.assertSearchKey255('\xfdm\x93_\x00P_\x1bL', ('<', 'V'))
1474
1475 def test_255_does_not_include_newline(self):
1476 # When mapping via _search_key_255, we should never have the '\n'
1477 # character, but all other 255 values should be present
1478 chars_used = set()
1479 for char_in in range(256):
1480 search_key = chk_map._search_key_255((chr(char_in),))
1481 chars_used.update(search_key)
1482 all_chars = set([chr(x) for x in range(256)])
1483 unused_chars = all_chars.symmetric_difference(chars_used)
1484 self.assertEqual(set('\n'), unused_chars)
1485
1486
1487class TestLeafNode(TestCaseWithStore):1456class TestLeafNode(TestCaseWithStore):
14881457
1489 def test_current_size_empty(self):1458 def test_current_size_empty(self):
@@ -1908,18 +1877,19 @@
1908 search_key_func = chk_map.search_key_registry.get('hash-255-way')1877 search_key_func = chk_map.search_key_registry.get('hash-255-way')
1909 node = InternalNode(search_key_func=search_key_func)1878 node = InternalNode(search_key_func=search_key_func)
1910 leaf1 = LeafNode(search_key_func=search_key_func)1879 leaf1 = LeafNode(search_key_func=search_key_func)
1911 leaf1.map(None, ('foo bar',), 'quux')1880 leaf1.map(None, StaticTuple('foo bar',), 'quux')
1912 leaf2 = LeafNode(search_key_func=search_key_func)1881 leaf2 = LeafNode(search_key_func=search_key_func)
1913 leaf2.map(None, ('strange',), 'beast')1882 leaf2.map(None, StaticTuple('strange',), 'beast')
1914 self.assertEqual('\xbeF\x014', search_key_func(('foo bar',)))1883 self.assertEqual('\xbeF\x014', search_key_func(StaticTuple('foo bar',)))
1915 self.assertEqual('\x85\xfa\xf7K', search_key_func(('strange',)))1884 self.assertEqual('\x85\xfa\xf7K', search_key_func(StaticTuple('strange',)))
1916 node.add_node("\xbe", leaf1)1885 node.add_node("\xbe", leaf1)
1917 # This sets up a path that should not be followed - it will error if1886 # This sets up a path that should not be followed - it will error if
1918 # the code tries to.1887 # the code tries to.
1919 node._items['\xbe'] = None1888 node._items['\xbe'] = None
1920 node.add_node("\x85", leaf2)1889 node.add_node("\x85", leaf2)
1921 self.assertEqual([(('strange',), 'beast')],1890 self.assertEqual([(('strange',), 'beast')],
1922 sorted(node.iteritems(None, [('strange',), ('weird',)])))1891 sorted(node.iteritems(None, [StaticTuple('strange',),
1892 StaticTuple('weird',)])))
19231893
1924 def test_iteritems_partial_empty(self):1894 def test_iteritems_partial_empty(self):
1925 node = InternalNode()1895 node = InternalNode()
@@ -1932,7 +1902,7 @@
1932 # Ensure test validity: nothing paged in below the root.1902 # Ensure test validity: nothing paged in below the root.
1933 self.assertEqual(2,1903 self.assertEqual(2,
1934 len([value for value in node._items.values()1904 len([value for value in node._items.values()
1935 if type(value) == tuple]))1905 if type(value) is StaticTuple]))
1936 # now, mapping to k3 should add a k3 leaf1906 # now, mapping to k3 should add a k3 leaf
1937 prefix, nodes = node.map(None, ('k3',), 'quux')1907 prefix, nodes = node.map(None, ('k3',), 'quux')
1938 self.assertEqual("k", prefix)1908 self.assertEqual("k", prefix)
@@ -1971,7 +1941,7 @@
1971 # Ensure test validity: nothing paged in below the root.1941 # Ensure test validity: nothing paged in below the root.
1972 self.assertEqual(2,1942 self.assertEqual(2,
1973 len([value for value in node._items.values()1943 len([value for value in node._items.values()
1974 if type(value) == tuple]))1944 if type(value) is StaticTuple]))
1975 # now, mapping to k23 causes k22 ('k2' in node) to split into k22 and1945 # now, mapping to k23 causes k22 ('k2' in node) to split into k22 and
1976 # k23, which for simplicity in the current implementation generates1946 # k23, which for simplicity in the current implementation generates
1977 # a new internal node between node, and k22/k23.1947 # a new internal node between node, and k22/k23.
@@ -2016,9 +1986,12 @@
2016 node = InternalNode(search_key_func=search_key_func)1986 node = InternalNode(search_key_func=search_key_func)
2017 node._key_width = 21987 node._key_width = 2
2018 node._node_width = 41988 node._node_width = 4
2019 self.assertEqual('E8B7BE43\x0071BEEFF9', search_key_func(('a', 'b')))1989 self.assertEqual('E8B7BE43\x0071BEEFF9', search_key_func(
2020 self.assertEqual('E8B7', node._search_prefix_filter(('a', 'b')))1990 StaticTuple('a', 'b')))
2021 self.assertEqual('E8B7', node._search_prefix_filter(('a',)))1991 self.assertEqual('E8B7', node._search_prefix_filter(
1992 StaticTuple('a', 'b')))
1993 self.assertEqual('E8B7', node._search_prefix_filter(
1994 StaticTuple('a',)))
20221995
2023 def test_unmap_k23_from_k1_k22_k23_gives_k1_k22_tree_new(self):1996 def test_unmap_k23_from_k1_k22_k23_gives_k1_k22_tree_new(self):
2024 chkmap = self._get_map(1997 chkmap = self._get_map(