/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/_known_graph_pyx.pyx

  • Committer: Martin Pool
  • Date: 2009-08-28 04:13:16 UTC
  • mfrom: (4634.6.8 2.0)
  • mto: This revision was merged to the branch mainline in revision 4660.
  • Revision ID: mbp@sourcefrog.net-20090828041316-adcbxfnfpc4bjtpl
Merge 2.0 back to trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
44
44
 
45
45
    void Py_INCREF(object)
46
46
 
 
47
import gc
47
48
 
48
 
from bzrlib import revision
 
49
from bzrlib import errors, revision
49
50
 
50
51
cdef object NULL_REVISION
51
52
NULL_REVISION = revision.NULL_REVISION
59
60
    cdef object children
60
61
    cdef public long gdfo
61
62
    cdef int seen
 
63
    cdef object extra
62
64
 
63
65
    def __init__(self, key):
64
 
        cdef int i
65
 
 
66
66
        self.key = key
67
67
        self.parents = None
68
68
 
70
70
        # Greatest distance from origin
71
71
        self.gdfo = -1
72
72
        self.seen = 0
 
73
        self.extra = None
73
74
 
74
75
    property child_keys:
75
76
        def __get__(self):
115
116
    return <_KnownGraphNode>temp_node
116
117
 
117
118
 
118
 
# TODO: slab allocate all _KnownGraphNode objects.
119
 
#       We already know how many we are going to need, except for a couple of
120
 
#       ghosts that could be allocated on demand.
 
119
cdef class _MergeSorter
121
120
 
122
121
cdef class KnownGraph:
123
122
    """This is a class which assumes we already know the full graph."""
136
135
        # Maps {sorted(revision_id, revision_id): heads}
137
136
        self._known_heads = {}
138
137
        self.do_cache = int(do_cache)
 
138
        # TODO: consider disabling gc since we are allocating a lot of nodes
 
139
        #       that won't be collectable anyway. real world testing has not
 
140
        #       shown a specific impact, yet.
139
141
        self._initialize_nodes(parent_map)
140
142
        self._find_gdfo()
141
143
 
183
185
            parent_keys = <object>temp_parent_keys
184
186
            num_parent_keys = len(parent_keys)
185
187
            node = self._get_or_create_node(key)
186
 
            # We know how many parents, so we could pre allocate an exact sized
187
 
            # tuple here
 
188
            # We know how many parents, so we pre allocate the tuple
188
189
            parent_nodes = PyTuple_New(num_parent_keys)
189
 
            # We use iter here, because parent_keys maybe be a list or tuple
190
190
            for pos2 from 0 <= pos2 < num_parent_keys:
 
191
                # Note: it costs us 10ms out of 40ms to lookup all of these
 
192
                #       parents, it doesn't seem to be an allocation overhead,
 
193
                #       but rather a lookup overhead. There doesn't seem to be
 
194
                #       a way around it, and that is one reason why
 
195
                #       KnownGraphNode maintains a direct pointer to the parent
 
196
                #       node.
 
197
                # We use [] because parent_keys may be a tuple or list
191
198
                parent_node = self._get_or_create_node(parent_keys[pos2])
192
199
                # PyTuple_SET_ITEM will steal a reference, so INCREF first
193
200
                Py_INCREF(parent_node)
335
342
        if self.do_cache:
336
343
            PyDict_SetItem(self._known_heads, heads_key, heads)
337
344
        return heads
 
345
 
 
346
    def topo_sort(self):
 
347
        """Return the nodes in topological order.
 
348
 
 
349
        All parents must occur before all children.
 
350
        """
 
351
        # This is, for the most part, the same iteration order that we used for
 
352
        # _find_gdfo, consider finding a way to remove the duplication
 
353
        # In general, we find the 'tails' (nodes with no parents), and then
 
354
        # walk to the children. For children that have all of their parents
 
355
        # yielded, we queue up the child to be yielded as well.
 
356
        cdef _KnownGraphNode node
 
357
        cdef _KnownGraphNode child
 
358
        cdef PyObject *temp
 
359
        cdef Py_ssize_t pos
 
360
        cdef int replace
 
361
        cdef Py_ssize_t last_item
 
362
 
 
363
        pending = self._find_tails()
 
364
        if PyList_GET_SIZE(pending) == 0 and len(self._nodes) > 0:
 
365
            raise errors.GraphCycleError(self._nodes)
 
366
 
 
367
        topo_order = []
 
368
 
 
369
        last_item = PyList_GET_SIZE(pending) - 1
 
370
        while last_item >= 0:
 
371
            # Avoid pop followed by push, instead, peek, and replace
 
372
            # timing shows this is 930ms => 770ms for OOo
 
373
            node = _get_list_node(pending, last_item)
 
374
            last_item = last_item - 1
 
375
            if node.parents is not None:
 
376
                # We don't include ghost parents
 
377
                PyList_Append(topo_order, node.key)
 
378
            for pos from 0 <= pos < PyList_GET_SIZE(node.children):
 
379
                child = _get_list_node(node.children, pos)
 
380
                if child.gdfo == -1:
 
381
                    # We know we have a graph cycle because a node has a parent
 
382
                    # which we couldn't find
 
383
                    raise errors.GraphCycleError(self._nodes)
 
384
                child.seen = child.seen + 1
 
385
                if child.seen == PyTuple_GET_SIZE(child.parents):
 
386
                    # All parents of this child have been yielded, queue this
 
387
                    # one to be yielded as well
 
388
                    last_item = last_item + 1
 
389
                    if last_item < PyList_GET_SIZE(pending):
 
390
                        Py_INCREF(child) # SetItem steals a ref
 
391
                        PyList_SetItem(pending, last_item, child)
 
392
                    else:
 
393
                        PyList_Append(pending, child)
 
394
                    # We have queued this node, we don't need to track it
 
395
                    # anymore
 
396
                    child.seen = 0
 
397
        # We started from the parents, so we don't need to do anymore work
 
398
        return topo_order
 
399
 
 
400
 
 
401
    def merge_sort(self, tip_key):
 
402
        """Compute the merge sorted graph output."""
 
403
        cdef _MergeSorter sorter
 
404
 
 
405
        # TODO: consider disabling gc since we are allocating a lot of nodes
 
406
        #       that won't be collectable anyway. real world testing has not
 
407
        #       shown a specific impact, yet.
 
408
        sorter = _MergeSorter(self, tip_key)
 
409
        return sorter.topo_order()
 
410
 
 
411
 
 
412
cdef class _MergeSortNode:
 
413
    """Tracks information about a node during the merge_sort operation."""
 
414
 
 
415
    # Public api
 
416
    cdef public object key
 
417
    cdef public long merge_depth
 
418
    cdef public object end_of_merge # True/False Is this the end of the current merge
 
419
 
 
420
    # Private api, used while computing the information
 
421
    cdef _KnownGraphNode left_parent
 
422
    cdef _KnownGraphNode left_pending_parent
 
423
    cdef object pending_parents # list of _KnownGraphNode for non-left parents
 
424
    cdef long _revno_first
 
425
    cdef long _revno_second
 
426
    cdef long _revno_last
 
427
    # TODO: turn these into flag/bit fields rather than individual members
 
428
    cdef int is_first_child # Is this the first child?
 
429
    cdef int seen_by_child # A child node has seen this parent
 
430
    cdef int completed # Fully Processed
 
431
 
 
432
    def __init__(self, key):
 
433
        self.key = key
 
434
        self.merge_depth = -1
 
435
        self.left_parent = None
 
436
        self.left_pending_parent = None
 
437
        self.pending_parents = None
 
438
        self._revno_first = -1
 
439
        self._revno_second = -1
 
440
        self._revno_last = -1
 
441
        self.is_first_child = 0
 
442
        self.seen_by_child = 0
 
443
        self.completed = 0
 
444
 
 
445
    def __repr__(self):
 
446
        return '%s(%s depth:%s rev:%s,%s,%s first:%s seen:%s)' % (
 
447
            self.__class__.__name__, self.key,
 
448
            self.merge_depth,
 
449
            self._revno_first, self._revno_second, self._revno_last,
 
450
            self.is_first_child, self.seen_by_child)
 
451
 
 
452
    cdef int has_pending_parents(self):
 
453
        if self.left_pending_parent is not None or self.pending_parents:
 
454
            return 1
 
455
        return 0
 
456
 
 
457
    cdef object _revno(self):
 
458
        if self._revno_first == -1:
 
459
            if self._revno_second != -1:
 
460
                raise RuntimeError('Something wrong with: %s' % (self,))
 
461
            return (self._revno_last,)
 
462
        else:
 
463
            return (self._revno_first, self._revno_second, self._revno_last)
 
464
 
 
465
    property revno:
 
466
        def __get__(self):
 
467
            return self._revno()
 
468
 
 
469
 
 
470
cdef class _MergeSorter:
 
471
    """This class does the work of computing the merge_sort ordering.
 
472
 
 
473
    We have some small advantages, in that we get all the extra information
 
474
    that KnownGraph knows, like knowing the child lists, etc.
 
475
    """
 
476
 
 
477
    # Current performance numbers for merge_sort(bzr_dev_parent_map):
 
478
    #  302ms tsort.merge_sort()
 
479
    #   91ms graph.KnownGraph().merge_sort()
 
480
    #   40ms kg.merge_sort()
 
481
 
 
482
    cdef KnownGraph graph
 
483
    cdef object _depth_first_stack  # list
 
484
    cdef Py_ssize_t _last_stack_item # offset to last item on stack
 
485
    # cdef object _ms_nodes # dict of key => _MergeSortNode
 
486
    cdef object _revno_to_branch_count # {revno => num child branches}
 
487
    cdef object _scheduled_nodes # List of nodes ready to be yielded
 
488
 
 
489
    def __init__(self, known_graph, tip_key):
 
490
        cdef _KnownGraphNode node
 
491
 
 
492
        self.graph = known_graph
 
493
        # self._ms_nodes = {}
 
494
        self._revno_to_branch_count = {}
 
495
        self._depth_first_stack = []
 
496
        self._last_stack_item = -1
 
497
        self._scheduled_nodes = []
 
498
        if (tip_key is not None and tip_key != NULL_REVISION
 
499
            and tip_key != (NULL_REVISION,)):
 
500
            node = self.graph._nodes[tip_key]
 
501
            self._push_node(node, 0)
 
502
 
 
503
    cdef _MergeSortNode _get_ms_node(self, _KnownGraphNode node):
 
504
        cdef PyObject *temp_node
 
505
        cdef _MergeSortNode ms_node
 
506
 
 
507
        if node.extra is None:
 
508
            ms_node = _MergeSortNode(node.key)
 
509
            node.extra = ms_node
 
510
        else:
 
511
            ms_node = <_MergeSortNode>node.extra
 
512
        return ms_node
 
513
 
 
514
    cdef _push_node(self, _KnownGraphNode node, long merge_depth):
 
515
        cdef _KnownGraphNode parent_node
 
516
        cdef _MergeSortNode ms_node, ms_parent_node
 
517
        cdef Py_ssize_t pos
 
518
 
 
519
        ms_node = self._get_ms_node(node)
 
520
        ms_node.merge_depth = merge_depth
 
521
        if node.parents is None:
 
522
            raise RuntimeError('ghost nodes should not be pushed'
 
523
                               ' onto the stack: %s' % (node,))
 
524
        if PyTuple_GET_SIZE(node.parents) > 0:
 
525
            parent_node = _get_parent(node.parents, 0)
 
526
            ms_node.left_parent = parent_node
 
527
            if parent_node.parents is None: # left-hand ghost
 
528
                ms_node.left_pending_parent = None
 
529
                ms_node.left_parent = None
 
530
            else:
 
531
                ms_node.left_pending_parent = parent_node
 
532
        if PyTuple_GET_SIZE(node.parents) > 1:
 
533
            ms_node.pending_parents = []
 
534
            for pos from 1 <= pos < PyTuple_GET_SIZE(node.parents):
 
535
                parent_node = _get_parent(node.parents, pos)
 
536
                if parent_node.parents is None: # ghost
 
537
                    continue
 
538
                PyList_Append(ms_node.pending_parents, parent_node)
 
539
 
 
540
        ms_node.is_first_child = 1
 
541
        if ms_node.left_parent is not None:
 
542
            ms_parent_node = self._get_ms_node(ms_node.left_parent)
 
543
            if ms_parent_node.seen_by_child:
 
544
                ms_node.is_first_child = 0
 
545
            ms_parent_node.seen_by_child = 1
 
546
        self._last_stack_item = self._last_stack_item + 1
 
547
        if self._last_stack_item < PyList_GET_SIZE(self._depth_first_stack):
 
548
            Py_INCREF(node) # SetItem steals a ref
 
549
            PyList_SetItem(self._depth_first_stack, self._last_stack_item,
 
550
                           node)
 
551
        else:
 
552
            PyList_Append(self._depth_first_stack, node)
 
553
 
 
554
    cdef _pop_node(self):
 
555
        cdef PyObject *temp
 
556
        cdef _MergeSortNode ms_node, ms_parent_node, ms_prev_node
 
557
        cdef _KnownGraphNode node, parent_node, prev_node
 
558
 
 
559
        node = _get_list_node(self._depth_first_stack, self._last_stack_item)
 
560
        ms_node = <_MergeSortNode>node.extra
 
561
        self._last_stack_item = self._last_stack_item - 1
 
562
        if ms_node.left_parent is not None:
 
563
            # Assign the revision number from the left-hand parent
 
564
            ms_parent_node = <_MergeSortNode>ms_node.left_parent.extra
 
565
            if ms_node.is_first_child:
 
566
                # First child just increments the final digit
 
567
                ms_node._revno_first = ms_parent_node._revno_first
 
568
                ms_node._revno_second = ms_parent_node._revno_second
 
569
                ms_node._revno_last = ms_parent_node._revno_last + 1
 
570
            else:
 
571
                # Not the first child, make a new branch
 
572
                #  (mainline_revno, branch_count, 1)
 
573
                if ms_parent_node._revno_first == -1:
 
574
                    # Mainline ancestor, the increment is on the last digit
 
575
                    base_revno = ms_parent_node._revno_last
 
576
                else:
 
577
                    base_revno = ms_parent_node._revno_first
 
578
                temp = PyDict_GetItem(self._revno_to_branch_count,
 
579
                                      base_revno)
 
580
                if temp == NULL:
 
581
                    branch_count = 1
 
582
                else:
 
583
                    branch_count = (<object>temp) + 1
 
584
                PyDict_SetItem(self._revno_to_branch_count, base_revno,
 
585
                               branch_count)
 
586
                ms_node._revno_first = base_revno
 
587
                ms_node._revno_second = branch_count
 
588
                ms_node._revno_last = 1
 
589
        else:
 
590
            temp = PyDict_GetItem(self._revno_to_branch_count, 0)
 
591
            if temp == NULL:
 
592
                # The first root node doesn't have a 3-digit revno
 
593
                root_count = 0
 
594
                ms_node._revno_first = -1
 
595
                ms_node._revno_second = -1
 
596
                ms_node._revno_last = 1
 
597
            else:
 
598
                root_count = (<object>temp) + 1
 
599
                ms_node._revno_first = 0
 
600
                ms_node._revno_second = root_count
 
601
                ms_node._revno_last = 1
 
602
            PyDict_SetItem(self._revno_to_branch_count, 0, root_count)
 
603
        ms_node.completed = 1
 
604
        if PyList_GET_SIZE(self._scheduled_nodes) == 0:
 
605
            # The first scheduled node is always the end of merge
 
606
            ms_node.end_of_merge = True
 
607
        else:
 
608
            prev_node = _get_list_node(self._scheduled_nodes,
 
609
                                    PyList_GET_SIZE(self._scheduled_nodes) - 1)
 
610
            ms_prev_node = <_MergeSortNode>prev_node.extra
 
611
            if ms_prev_node.merge_depth < ms_node.merge_depth:
 
612
                # The previously pushed node is to our left, so this is the end
 
613
                # of this right-hand chain
 
614
                ms_node.end_of_merge = True
 
615
            elif (ms_prev_node.merge_depth == ms_node.merge_depth
 
616
                  and prev_node not in node.parents):
 
617
                # The next node is not a direct parent of this node
 
618
                ms_node.end_of_merge = True
 
619
            else:
 
620
                ms_node.end_of_merge = False
 
621
        PyList_Append(self._scheduled_nodes, node)
 
622
 
 
623
    cdef _schedule_stack(self):
 
624
        cdef _KnownGraphNode last_node, next_node
 
625
        cdef _MergeSortNode ms_node, ms_last_node, ms_next_node
 
626
        cdef long next_merge_depth
 
627
        ordered = []
 
628
        while self._last_stack_item >= 0:
 
629
            # Peek at the last item on the stack
 
630
            last_node = _get_list_node(self._depth_first_stack,
 
631
                                       self._last_stack_item)
 
632
            if last_node.gdfo == -1:
 
633
                # if _find_gdfo skipped a node, that means there is a graph
 
634
                # cycle, error out now
 
635
                raise errors.GraphCycleError(self.graph._nodes)
 
636
            ms_last_node = <_MergeSortNode>last_node.extra
 
637
            if not ms_last_node.has_pending_parents():
 
638
                # Processed all parents, pop this node
 
639
                self._pop_node()
 
640
                continue
 
641
            while ms_last_node.has_pending_parents():
 
642
                if ms_last_node.left_pending_parent is not None:
 
643
                    # recurse depth first into the primary parent
 
644
                    next_node = ms_last_node.left_pending_parent
 
645
                    ms_last_node.left_pending_parent = None
 
646
                else:
 
647
                    # place any merges in right-to-left order for scheduling
 
648
                    # which gives us left-to-right order after we reverse
 
649
                    # the scheduled queue.
 
650
                    # Note: This has the effect of allocating common-new
 
651
                    #       revisions to the right-most subtree rather than the
 
652
                    #       left most, which will display nicely (you get
 
653
                    #       smaller trees at the top of the combined merge).
 
654
                    next_node = ms_last_node.pending_parents.pop()
 
655
                ms_next_node = self._get_ms_node(next_node)
 
656
                if ms_next_node.completed:
 
657
                    # this parent was completed by a child on the
 
658
                    # call stack. skip it.
 
659
                    continue
 
660
                # otherwise transfer it from the source graph into the
 
661
                # top of the current depth first search stack.
 
662
 
 
663
                if next_node is ms_last_node.left_parent:
 
664
                    next_merge_depth = ms_last_node.merge_depth
 
665
                else:
 
666
                    next_merge_depth = ms_last_node.merge_depth + 1
 
667
                self._push_node(next_node, next_merge_depth)
 
668
                # and do not continue processing parents until this 'call'
 
669
                # has recursed.
 
670
                break
 
671
 
 
672
    cdef topo_order(self):
 
673
        cdef _MergeSortNode ms_node
 
674
        cdef _KnownGraphNode node
 
675
        cdef Py_ssize_t pos
 
676
        cdef PyObject *temp_key, *temp_node
 
677
 
 
678
        # Note: allocating a _MergeSortNode and deallocating it for all nodes
 
679
        #       costs approx 8.52ms (21%) of the total runtime
 
680
        #       We might consider moving the attributes into the base
 
681
        #       KnownGraph object.
 
682
        self._schedule_stack()
 
683
 
 
684
        # We've set up the basic schedule, now we can continue processing the
 
685
        # output.
 
686
        # Note: This final loop costs us 40.0ms => 28.8ms (11ms, 25%) on
 
687
        #       bzr.dev, to convert the internal Object representation into a
 
688
        #       Tuple representation...
 
689
        #       2ms is walking the data and computing revno tuples
 
690
        #       7ms is computing the return tuple
 
691
        #       4ms is PyList_Append()
 
692
        ordered = []
 
693
        # output the result in reverse order, and separate the generated info
 
694
        for pos from PyList_GET_SIZE(self._scheduled_nodes) > pos >= 0:
 
695
            node = _get_list_node(self._scheduled_nodes, pos)
 
696
            ms_node = <_MergeSortNode>node.extra
 
697
            PyList_Append(ordered, ms_node)
 
698
            node.extra = None
 
699
        # Clear out the scheduled nodes now that we're done
 
700
        self._scheduled_nodes = []
 
701
        return ordered