335
342
if self.do_cache:
336
343
PyDict_SetItem(self._known_heads, heads_key, heads)
347
"""Return the nodes in topological order.
349
All parents must occur before all children.
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
361
cdef Py_ssize_t last_item
363
pending = self._find_tails()
364
if PyList_GET_SIZE(pending) == 0 and len(self._nodes) > 0:
365
raise errors.GraphCycleError(self._nodes)
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)
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)
393
PyList_Append(pending, child)
394
# We have queued this node, we don't need to track it
397
# We started from the parents, so we don't need to do anymore work
401
def merge_sort(self, tip_key):
402
"""Compute the merge sorted graph output."""
403
cdef _MergeSorter sorter
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()
412
cdef class _MergeSortNode:
413
"""Tracks information about a node during the merge_sort operation."""
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
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
432
def __init__(self, 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
446
return '%s(depth:%s rev:%s,%s,%s first:%s seen:%s)' % (self.__class__.__name__,
448
self._revno_first, self._revno_second, self._revno_last,
449
self.is_first_child, self.seen_by_child)
451
cdef int has_pending_parents(self):
452
if self.left_pending_parent is not None or self.pending_parents:
456
cdef object _revno(self):
457
if self._revno_first == -1:
458
if self._revno_second != -1:
459
raise RuntimeError('Something wrong with: %s' % (self,))
460
return (self._revno_last,)
462
return (self._revno_first, self._revno_second, self._revno_last)
469
cdef class _MergeSorter:
470
"""This class does the work of computing the merge_sort ordering.
472
We have some small advantages, in that we get all the extra information
473
that KnownGraph knows, like knowing the child lists, etc.
476
# Current performance numbers for merge_sort(bzr_dev_parent_map):
477
# 302ms tsort.merge_sort()
478
# 91ms graph.KnownGraph().merge_sort()
479
# 40ms kg.merge_sort()
481
cdef KnownGraph graph
482
cdef object _depth_first_stack # list
483
cdef Py_ssize_t _last_stack_item # offset to last item on stack
484
# cdef object _ms_nodes # dict of key => _MergeSortNode
485
cdef object _revno_to_branch_count # {revno => num child branches}
486
cdef object _scheduled_nodes # List of nodes ready to be yielded
488
def __init__(self, known_graph, tip_key):
489
cdef _KnownGraphNode node
491
self.graph = known_graph
492
# self._ms_nodes = {}
493
self._revno_to_branch_count = {}
494
self._depth_first_stack = []
495
self._last_stack_item = -1
496
self._scheduled_nodes = []
497
if (tip_key is not None and tip_key != NULL_REVISION
498
and tip_key != (NULL_REVISION,)):
499
node = self.graph._nodes[tip_key]
500
self._get_ms_node(node)
501
self._push_node(node, 0)
503
cdef _MergeSortNode _get_ms_node(self, _KnownGraphNode node):
504
cdef PyObject *temp_node
505
cdef _MergeSortNode ms_node
507
if node.extra is None:
508
ms_node = _MergeSortNode(node.key)
511
ms_node = <_MergeSortNode>node.extra
514
cdef _push_node(self, _KnownGraphNode node, long merge_depth):
515
cdef _KnownGraphNode parent_node
516
cdef _MergeSortNode ms_node, ms_parent_node
519
ms_node = self._get_ms_node(node)
520
ms_node.merge_depth = merge_depth
521
if PyTuple_GET_SIZE(node.parents) > 0:
522
parent_node = _get_parent(node.parents, 0)
523
ms_node.left_parent = parent_node
524
ms_node.left_pending_parent = parent_node
525
if PyTuple_GET_SIZE(node.parents) > 1:
526
ms_node.pending_parents = []
527
for pos from 1 <= pos < PyTuple_GET_SIZE(node.parents):
528
parent_node = _get_parent(node.parents, pos)
529
if parent_node.parents is None: # ghost
531
PyList_Append(ms_node.pending_parents, parent_node)
533
ms_node.is_first_child = 1
534
if ms_node.left_parent is not None:
535
ms_parent_node = self._get_ms_node(ms_node.left_parent)
536
if ms_parent_node.seen_by_child:
537
ms_node.is_first_child = 0
538
ms_parent_node.seen_by_child = 1
539
self._last_stack_item = self._last_stack_item + 1
540
if self._last_stack_item < PyList_GET_SIZE(self._depth_first_stack):
541
Py_INCREF(node) # SetItem steals a ref
542
PyList_SetItem(self._depth_first_stack, self._last_stack_item,
545
PyList_Append(self._depth_first_stack, node)
547
cdef _pop_node(self):
549
cdef _MergeSortNode ms_node, ms_parent_node, ms_prev_node
550
cdef _KnownGraphNode node, parent_node, prev_node
552
node = _get_list_node(self._depth_first_stack, self._last_stack_item)
553
ms_node = <_MergeSortNode>node.extra
554
self._last_stack_item = self._last_stack_item - 1
555
if ms_node.left_parent is not None:
556
# Assign the revision number from the left-hand parent
557
ms_parent_node = <_MergeSortNode>ms_node.left_parent.extra
558
if ms_node.is_first_child:
559
# First child just increments the final digit
560
ms_node._revno_first = ms_parent_node._revno_first
561
ms_node._revno_second = ms_parent_node._revno_second
562
ms_node._revno_last = ms_parent_node._revno_last + 1
564
# Not the first child, make a new branch
565
# (mainline_revno, branch_count, 1)
566
if ms_parent_node._revno_first == -1:
567
# Mainline ancestor, the increment is on the last digit
568
base_revno = ms_parent_node._revno_last
570
base_revno = ms_parent_node._revno_first
571
temp = PyDict_GetItem(self._revno_to_branch_count,
576
branch_count = (<object>temp) + 1
577
PyDict_SetItem(self._revno_to_branch_count, base_revno,
579
ms_node._revno_first = base_revno
580
ms_node._revno_second = branch_count
581
ms_node._revno_last = 1
583
temp = PyDict_GetItem(self._revno_to_branch_count, 0)
585
# The first root node doesn't have a 3-digit revno
587
ms_node._revno_first = -1
588
ms_node._revno_second = -1
589
ms_node._revno_last = 1
591
root_count = (<object>temp) + 1
592
ms_node._revno_first = 0
593
ms_node._revno_second = root_count
594
ms_node._revno_last = 1
595
PyDict_SetItem(self._revno_to_branch_count, 0, root_count)
596
ms_node.completed = 1
597
if PyList_GET_SIZE(self._scheduled_nodes) == 0:
598
# The first scheduled node is always the end of merge
599
ms_node.end_of_merge = True
601
prev_node = _get_list_node(self._scheduled_nodes,
602
PyList_GET_SIZE(self._scheduled_nodes) - 1)
603
ms_prev_node = <_MergeSortNode>prev_node.extra
604
if ms_prev_node.merge_depth < ms_node.merge_depth:
605
# The previously pushed node is to our left, so this is the end
606
# of this right-hand chain
607
ms_node.end_of_merge = True
608
elif (ms_prev_node.merge_depth == ms_node.merge_depth
609
and prev_node not in node.parents):
610
# The next node is not a direct parent of this node
611
ms_node.end_of_merge = True
613
ms_node.end_of_merge = False
614
PyList_Append(self._scheduled_nodes, node)
616
cdef _schedule_stack(self):
617
cdef _KnownGraphNode last_node, next_node
618
cdef _MergeSortNode ms_node, ms_last_node, ms_next_node
619
cdef long next_merge_depth
621
while self._last_stack_item >= 0:
622
# Peek at the last item on the stack
623
last_node = _get_list_node(self._depth_first_stack,
624
self._last_stack_item)
625
if last_node.gdfo == -1:
626
# if _find_gdfo skipped a node, that means there is a graph
627
# cycle, error out now
628
raise errors.GraphCycleError(self.graph._nodes)
629
ms_last_node = <_MergeSortNode>last_node.extra
630
if not ms_last_node.has_pending_parents():
631
# Processed all parents, pop this node
634
while ms_last_node.has_pending_parents():
635
if ms_last_node.left_pending_parent is not None:
636
# recurse depth first into the primary parent
637
next_node = ms_last_node.left_pending_parent
638
ms_last_node.left_pending_parent = None
640
# place any merges in right-to-left order for scheduling
641
# which gives us left-to-right order after we reverse
642
# the scheduled queue.
643
# Note: This has the effect of allocating common-new
644
# revisions to the right-most subtree rather than the
645
# left most, which will display nicely (you get
646
# smaller trees at the top of the combined merge).
647
next_node = ms_last_node.pending_parents.pop()
648
ms_next_node = self._get_ms_node(next_node)
649
if ms_next_node.completed:
650
# this parent was completed by a child on the
651
# call stack. skip it.
653
# otherwise transfer it from the source graph into the
654
# top of the current depth first search stack.
656
if next_node is ms_last_node.left_parent:
657
next_merge_depth = ms_last_node.merge_depth
659
next_merge_depth = ms_last_node.merge_depth + 1
660
self._push_node(next_node, next_merge_depth)
661
# and do not continue processing parents until this 'call'
665
cdef topo_order(self):
666
cdef _MergeSortNode ms_node
667
cdef _KnownGraphNode node
669
cdef PyObject *temp_key, *temp_node
671
# Note: allocating a _MergeSortNode and deallocating it for all nodes
672
# costs approx 8.52ms (21%) of the total runtime
673
# We might consider moving the attributes into the base
675
self._schedule_stack()
677
# We've set up the basic schedule, now we can continue processing the
679
# Note: This final loop costs us 40.0ms => 28.8ms (11ms, 25%) on
680
# bzr.dev, to convert the internal Object representation into a
681
# Tuple representation...
682
# 2ms is walking the data and computing revno tuples
683
# 7ms is computing the return tuple
684
# 4ms is PyList_Append()
686
# output the result in reverse order, and separate the generated info
687
for pos from PyList_GET_SIZE(self._scheduled_nodes) > pos >= 0:
688
node = _get_list_node(self._scheduled_nodes, pos)
689
ms_node = <_MergeSortNode>node.extra
690
PyList_Append(ordered, ms_node)
692
# Clear out the scheduled nodes now that we're done
693
self._scheduled_nodes = []