~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/groupcompress.py

  • Committer: John Arbash Meinel
  • Date: 2010-08-05 16:27:35 UTC
  • mto: This revision was merged to the branch mainline in revision 5374.
  • Revision ID: john@arbash-meinel.com-20100805162735-172opvx34sr5gpbl
Find a case where we are wasting a bit of memory.

Specifically the 'build_details' tuple contains a lot of wasted references,
and we hold on to one of these for each record we are fetching.
And for something like 'bzr pack', that is all keys.

For just loading all text build details on my bzr+ repository, With:
locations = b.repository.texts._index.get_build_details(b.repository.texts.keys())
This drops the memory consumption from:
WorkingSize   77604KiB
 to
WorkingSize   64640KiB

Or around 10.6MB. I worked it out to a savings of about 80 bytes/record
on data that can have hundreds of thousands of records (in 32-bit).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1809
1809
        return result
1810
1810
 
1811
1811
 
 
1812
class _GCBuildDetails(object):
 
1813
    """A blob of data about the build details.
 
1814
 
 
1815
    This stores the minimal data, which then allows compatibility with the old
 
1816
    api, without taking as much memory.
 
1817
    """
 
1818
 
 
1819
    __slots__ = ('_index', '_group_start', '_group_end', '_basis_end',
 
1820
                 '_delta_end', '_parents')
 
1821
 
 
1822
    method = 'group'
 
1823
    compression_parent = None
 
1824
 
 
1825
    def __init__(self, parents, position_info):
 
1826
        self._parents = parents
 
1827
        self._index = position_info[0]
 
1828
        self._group_start = position_info[1]
 
1829
        # Is this _end or length? Doesn't really matter to us
 
1830
        self._group_end = position_info[2]
 
1831
        self._basis_end = position_info[3]
 
1832
        self._delta_end = position_info[4]
 
1833
 
 
1834
    def __repr__(self):
 
1835
        return '%s(%s, %s)' % (self.__class__.__name__,
 
1836
            self.index_memo, self._parents)
 
1837
 
 
1838
    @property
 
1839
    def index_memo(self):
 
1840
        return (self._index, self._group_start, self._group_end,
 
1841
                self._basis_end, self._delta_end)
 
1842
 
 
1843
    @property
 
1844
    def record_details(self):
 
1845
        return static_tuple.StaticTuple(self.method, None)
 
1846
 
 
1847
    def __getitem__(self, offset):
 
1848
        """Compatibility thunk to act like a tuple."""
 
1849
        if offset == 0:
 
1850
            return self.index_memo
 
1851
        elif offset == 1:
 
1852
            return self.compression_parent # Always None
 
1853
        elif offset == 2:
 
1854
            return self._parents
 
1855
        elif offset == 3:
 
1856
            return self.record_details
 
1857
        else:
 
1858
            raise IndexError('offset out of range')
 
1859
            
 
1860
    def __len__(self):
 
1861
        return 4
 
1862
 
 
1863
 
1812
1864
class _GCGraphIndex(object):
1813
1865
    """Mapper from GroupCompressVersionedFiles needs into GraphIndex storage."""
1814
1866
 
2009
2061
                parents = None
2010
2062
            else:
2011
2063
                parents = entry[3][0]
2012
 
            method = 'group'
2013
 
            result[key] = (self._node_to_position(entry),
2014
 
                                  None, parents, (method, None))
 
2064
            details = _GCBuildDetails(parents, self._node_to_position(entry))
 
2065
            result[key] = details
2015
2066
        return result
2016
2067
 
2017
2068
    def keys(self):
2033
2084
        # each, or about 7MB. Note that it might be even more when you consider
2034
2085
        # how PyInt is allocated in separate slabs. And you can't return a slab
2035
2086
        # to the OS if even 1 int on it is in use. Note though that Python uses
2036
 
        # a LIFO when re-using PyInt slots, which probably causes more
 
2087
        # a LIFO when re-using PyInt slots, which might cause more
2037
2088
        # fragmentation.
2038
2089
        start = int(bits[0])
2039
2090
        start = self._int_cache.setdefault(start, start)