~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/xml8.py

  • Committer: John Arbash Meinel
  • Date: 2008-07-11 21:41:24 UTC
  • mto: This revision was merged to the branch mainline in revision 3543.
  • Revision ID: john@arbash-meinel.com-20080711214124-qi09irlj7pd5cuzg
Shortcut the case when one revision is in the ancestry of the other.

At the cost of a heads() check, when one parent supersedes, we don't have to extract
the text for the other. Changes merge time from 3m37s => 3m21s. Using a
CachingParentsProvider would drop the time down to 3m11s.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
    errors,
23
23
    inventory,
24
24
    revision as _mod_revision,
25
 
    trace,
26
25
    )
27
26
from bzrlib.xml_serializer import SubElement, Element, Serializer
28
27
from bzrlib.inventory import ROOT_ID, Inventory, InventoryEntry
167
166
        if inv.root.revision is None:
168
167
            raise AssertionError()
169
168
 
170
 
    def _check_cache_size(self, inv_size, entry_cache):
171
 
        """Check that the entry_cache is large enough.
172
 
 
173
 
        We want the cache to be ~2x the size of an inventory. The reason is
174
 
        because we use a FIFO cache, and how Inventory records are likely to
175
 
        change. In general, you have a small number of records which change
176
 
        often, and a lot of records which do not change at all. So when the
177
 
        cache gets full, you actually flush out a lot of the records you are
178
 
        interested in, which means you need to recreate all of those records.
179
 
        An LRU Cache would be better, but the overhead negates the cache
180
 
        coherency benefit.
181
 
 
182
 
        One way to look at it, only the size of the cache > len(inv) is your
183
 
        'working' set. And in general, it shouldn't be a problem to hold 2
184
 
        inventories in memory anyway.
185
 
 
186
 
        :param inv_size: The number of entries in an inventory.
187
 
        """
188
 
        if entry_cache is None:
189
 
            return
190
 
        # 1.5 times might also be reasonable.
191
 
        recommended_min_cache_size = inv_size * 1.5
192
 
        if entry_cache.cache_size() < recommended_min_cache_size:
193
 
            recommended_cache_size = inv_size * 2
194
 
            trace.mutter('Resizing the inventory entry cache from %d to %d',
195
 
                         entry_cache.cache_size(), recommended_cache_size)
196
 
            entry_cache.resize(recommended_cache_size)
197
 
 
198
169
    def write_inventory_to_lines(self, inv):
199
170
        """Return a list of lines with the encoded inventory."""
200
171
        return self.write_inventory(inv, None)
366
337
            prop_elt.tail = '\n'
367
338
        top_elt.tail = '\n'
368
339
 
369
 
    def _unpack_inventory(self, elt, revision_id=None, entry_cache=None):
 
340
    def _unpack_inventory(self, elt, revision_id=None):
370
341
        """Construct from XML Element"""
371
342
        if elt.tag != 'inventory':
372
343
            raise errors.UnexpectedInventoryFormat('Root tag is %r' % elt.tag)
379
350
            revision_id = cache_utf8.encode(revision_id)
380
351
        inv = inventory.Inventory(root_id=None, revision_id=revision_id)
381
352
        for e in elt:
382
 
            ie = self._unpack_entry(e, entry_cache=entry_cache)
 
353
            ie = self._unpack_entry(e)
383
354
            inv.add(ie)
384
 
        self._check_cache_size(len(inv), entry_cache)
385
355
        return inv
386
356
 
387
 
    def _unpack_entry(self, elt, entry_cache=None):
388
 
        elt_get = elt.get
389
 
        file_id = elt_get('file_id')
390
 
        revision = elt_get('revision')
391
 
        # Check and see if we have already unpacked this exact entry
392
 
        # Some timings for "repo.revision_trees(last_100_revs)"
393
 
        #               bzr     mysql
394
 
        #   unmodified  4.1s    40.8s
395
 
        #   using lru   3.5s
396
 
        #   using fifo  2.83s   29.1s
397
 
        #   lru._cache  2.8s
398
 
        #   dict        2.75s   26.8s
399
 
        #   inv.add     2.5s    26.0s
400
 
        #   no_copy     2.00s   20.5s
401
 
        #   no_c,dict   1.95s   18.0s
402
 
        # Note that a cache of 10k nodes is more than sufficient to hold all of
403
 
        # the inventory for the last 100 revs for bzr, but not for mysql (20k
404
 
        # is enough for mysql, which saves the same 2s as using a dict)
405
 
 
406
 
        # Breakdown of mysql using time.clock()
407
 
        #   4.1s    2 calls to element.get for file_id, revision_id
408
 
        #   4.5s    cache_hit lookup
409
 
        #   7.1s    InventoryFile.copy()
410
 
        #   2.4s    InventoryDirectory.copy()
411
 
        #   0.4s    decoding unique entries
412
 
        #   1.6s    decoding entries after FIFO fills up
413
 
        #   0.8s    Adding nodes to FIFO (including flushes)
414
 
        #   0.1s    cache miss lookups
415
 
        # Using an LRU cache
416
 
        #   4.1s    2 calls to element.get for file_id, revision_id
417
 
        #   9.9s    cache_hit lookup
418
 
        #   10.8s   InventoryEntry.copy()
419
 
        #   0.3s    cache miss lookus
420
 
        #   1.2s    decoding entries
421
 
        #   1.0s    adding nodes to LRU
422
 
        if entry_cache is not None and revision is not None:
423
 
            key = (file_id, revision)
424
 
            try:
425
 
                # We copy it, because some operatations may mutate it
426
 
                cached_ie = entry_cache[key]
427
 
            except KeyError:
428
 
                pass
429
 
            else:
430
 
                # Only copying directory entries drops us 2.85s => 2.35s
431
 
                # if cached_ie.kind == 'directory':
432
 
                #     return cached_ie.copy()
433
 
                # return cached_ie
434
 
                return cached_ie.copy()
435
 
 
 
357
    def _unpack_entry(self, elt):
436
358
        kind = elt.tag
437
359
        if not InventoryEntry.versionable_kind(kind):
438
360
            raise AssertionError('unsupported entry kind %s' % kind)
439
361
 
440
362
        get_cached = _get_utf8_or_ascii
441
363
 
442
 
        file_id = get_cached(file_id)
443
 
        if revision is not None:
444
 
            revision = get_cached(revision)
445
 
        parent_id = elt_get('parent_id')
 
364
        parent_id = elt.get('parent_id')
446
365
        if parent_id is not None:
447
366
            parent_id = get_cached(parent_id)
 
367
        file_id = get_cached(elt.get('file_id'))
448
368
 
449
369
        if kind == 'directory':
450
370
            ie = inventory.InventoryDirectory(file_id,
451
 
                                              elt_get('name'),
 
371
                                              elt.get('name'),
452
372
                                              parent_id)
453
373
        elif kind == 'file':
454
374
            ie = inventory.InventoryFile(file_id,
455
 
                                         elt_get('name'),
 
375
                                         elt.get('name'),
456
376
                                         parent_id)
457
 
            ie.text_sha1 = elt_get('text_sha1')
458
 
            if elt_get('executable') == 'yes':
 
377
            ie.text_sha1 = elt.get('text_sha1')
 
378
            if elt.get('executable') == 'yes':
459
379
                ie.executable = True
460
 
            v = elt_get('text_size')
 
380
            v = elt.get('text_size')
461
381
            ie.text_size = v and int(v)
462
382
        elif kind == 'symlink':
463
383
            ie = inventory.InventoryLink(file_id,
464
 
                                         elt_get('name'),
 
384
                                         elt.get('name'),
465
385
                                         parent_id)
466
 
            ie.symlink_target = elt_get('symlink_target')
 
386
            ie.symlink_target = elt.get('symlink_target')
467
387
        else:
468
388
            raise errors.UnsupportedInventoryKind(kind)
 
389
        revision = elt.get('revision')
 
390
        if revision is not None:
 
391
            revision = get_cached(revision)
469
392
        ie.revision = revision
470
 
        if revision is not None and entry_cache is not None:
471
 
            # We cache a copy() because callers like to mutate objects, and
472
 
            # that would cause the item in cache to mutate as well.
473
 
            # This has a small effect on many-inventory performance, because
474
 
            # the majority fraction is spent in cache hits, not misses.
475
 
            entry_cache[key] = ie.copy()
476
393
 
477
394
        return ie
478
395