[pypy-svn] r56048 - in pypy/dist/pypy/tool/algo: . test

arigo at codespeak.net arigo at codespeak.net
Tue Jun 24 10:25:06 CEST 2008


Author: arigo
Date: Tue Jun 24 10:25:03 2008
New Revision: 56048

Modified:
   pypy/dist/pypy/tool/algo/graphlib.py
   pypy/dist/pypy/tool/algo/test/test_graphlib.py
Log:
Change the stack check insertion logic.  Now, when translating pypy:

- it runs in about 35 secs, only slightly slower than before this
   check-in

- it puts 4 times more stack checks than before

- but when the pypy-c runs pystone or richards, there is a
   lower number of calls to stack_check being actually done
   and it gives a minor speed-up.

All in all the check-in makes sense also because the algorithm sounds
kind of reasonable, and a bit more deterministic (which could reduce
random effects from translation to translation).



Modified: pypy/dist/pypy/tool/algo/graphlib.py
==============================================================================
--- pypy/dist/pypy/tool/algo/graphlib.py	(original)
+++ pypy/dist/pypy/tool/algo/graphlib.py	Tue Jun 24 10:25:03 2008
@@ -236,64 +236,60 @@
     """Enumerates a reasonably minimal set of vertices that must be removed to
     make the graph acyclic."""
 
-    # the approach is as follows: starting from each root, find some set
-    # of cycles using a simple depth-first search. Then break the
-    # vertex that is part of the most cycles.  Repeat.
+    # Consider where each cycle should be broken -- we go for the idea
+    # that it is often better to break it as far as possible from the
+    # cycle's entry point, so that the stack check occurs as late as
+    # possible.  For the distance we use a global "depth" computed as
+    # the distance from the roots.  The algo below is:
+    #  - get a list of cycles
+    #  - let maxdepth(cycle) = max(depth(vertex) for vertex in cycle)
+    #  - sort the list of cycles by their maxdepth, nearest first
+    #  - for each cycle in the list, if the cycle is not broken yet,
+    #      remove the vertex with the largest depth
+    #  - repeat the whole procedure until no more cycles are found.
+    # Ordering the cycles themselves nearest first maximizes the chances
+    # that when breaking a nearby cycle - which must be broken in any
+    # case - we remove a vertex and break some further cycles by chance.
 
-    remaining_vertices = vertices.copy()
+    v_depths = vertices
     progress = True
     roots_finished = set()
-    v_depths = None
     while progress:
-        roots = list(find_roots(remaining_vertices, edges))
-        if v_depths is None:
-            #print roots
-            v_depths = compute_depths(roots, remaining_vertices, edges)
-            assert len(v_depths) == len(remaining_vertices)
-            #print v_depths
-            factor = 1.0 / len(v_depths)
-        #print '%d inital roots' % (len(roots,))
+        roots = list(find_roots(v_depths, edges))
+        if v_depths is vertices:  # first time only
+            v_depths = compute_depths(roots, vertices, edges)
+            assert len(v_depths) == len(vertices)  # ...so far.  We remove
+            # from v_depths the vertices at which we choose to break cycles
+        print '%d inital roots' % (len(roots,))
         progress = False
         for root in roots:
             if root in roots_finished:
                 continue
-            cycles = all_cycles(root, remaining_vertices, edges)
+            cycles = all_cycles(root, v_depths, edges)
             if not cycles:
                 roots_finished.add(root)
                 continue
-            #print 'from root %r: %d cycles' % (root, len(cycles))
-            allcycles = {}
-            v2cycles = {}
+            print 'from root %r: %d cycles' % (root, len(cycles))
+            # compute the "depth" of each cycles: how far it goes from any root
+            allcycles = []
             for cycle in cycles:
-                allcycles[id(cycle)] = cycle
-                for edge in cycle:
-                    v2cycles.setdefault(edge.source, []).append(id(cycle))
-            v_weights = {}
-            for v, cycles in v2cycles.iteritems():
-                v_weights[v] = len(cycles) + v_depths.get(v, 0) * factor
-                # The weight of a vertex is the number of cycles going
-                # through it, plus a small value used as a bias in case of
-                # a tie.  The bias is in favor of vertices of large depth.
-            while allcycles:
-                max_weight = 1
-                max_vertex = None
-                for v, weight in v_weights.iteritems():
-                    if weight >= max_weight:
-                        max_vertex = v
-                        max_weight = weight
-                if max_vertex is None:
-                    break
-                # kill this vertex
-                yield max_vertex
-                progress = True
-                # unregister all cycles that have just been broken
-                for broken_cycle_id in v2cycles[max_vertex]:
-                    broken_cycle = allcycles.pop(broken_cycle_id, ())
-                    for edge in broken_cycle:
-                        v_weights[edge.source] -= 1
-
-                del remaining_vertices[max_vertex]
-    assert is_acyclic(remaining_vertices, edges)
+                cycledepth = max([v_depths[edge.source] for edge in cycle])
+                allcycles.append((cycledepth, cycle))
+            allcycles.sort()
+            # consider all cycles starting from the ones with smallest depth
+            for _, cycle in allcycles:
+                try:
+                    choices = [(v_depths[edge.source], edge.source)
+                               for edge in cycle]
+                except KeyError:
+                    pass   # this cycle was already broken
+                else:
+                    # break this cycle by removing the furthest vertex
+                    max_depth, max_vertex = max(choices)
+                    del v_depths[max_vertex]
+                    yield max_vertex
+                    progress = True
+    assert is_acyclic(v_depths, edges)
 
 
 def show_graph(vertices, edges):

Modified: pypy/dist/pypy/tool/algo/test/test_graphlib.py
==============================================================================
--- pypy/dist/pypy/tool/algo/test/test_graphlib.py	(original)
+++ pypy/dist/pypy/tool/algo/test/test_graphlib.py	Tue Jun 24 10:25:03 2008
@@ -67,7 +67,7 @@
         result.sort()
         assert ''.join(result) == 'AD'
         # the answers 'BD' and 'DE' are correct too, but 'AD' should
-        # be picked because 'A' is the node cycle that is the further
+        # be picked because 'A' is the cycle's node that is the further
         # from the root 'R'.
 
     def test_find_roots(self):
@@ -227,8 +227,9 @@
         # assert is_acyclic(): included in break_cycles() itself
 
     def test_break_cycles_v(self):
-        list(break_cycles_v(self.edges, self.edges))
+        result = list(break_cycles_v(self.edges, self.edges))
         # assert is_acyclic(): included in break_cycles_v() itself
+        print len(result), 'vertices removed'
 
     def test_find_roots(self):
         roots = find_roots(self.edges, self.edges)



More information about the Pypy-commit mailing list