[pypy-svn] r55961 - in pypy/dist/pypy: tool/algo tool/algo/test translator

arigo at codespeak.net arigo at codespeak.net
Thu Jun 19 14:23:28 CEST 2008


Author: arigo
Date: Thu Jun 19 14:23:27 2008
New Revision: 55961

Modified:
   pypy/dist/pypy/tool/algo/graphlib.py
   pypy/dist/pypy/tool/algo/test/test_graphlib.py
   pypy/dist/pypy/translator/transform.py
Log:
Improve the results of the stack check insertion algo.  This is done by
using essentially the same algo, but detecting vertices instead of edges
to remove.  The vertices in this case are the blocks that contain the
calls.

Another improvement (which also should make results a bit more
deterministic) is a bias that prefers picking vertices that are "as far
as possible" from the root vertices.


Modified: pypy/dist/pypy/tool/algo/graphlib.py
==============================================================================
--- pypy/dist/pypy/tool/algo/graphlib.py	(original)
+++ pypy/dist/pypy/tool/algo/graphlib.py	Thu Jun 19 14:23:27 2008
@@ -71,11 +71,11 @@
                             if discovery_time[wroot] < discovery_time[vroot]:
                                 vroot = wroot
                     if vroot == v:
-                        component = {}
+                        component = set()
                         while True:
                             w = stack.pop()
                             del component_root[w]
-                            component[w] = True
+                            component.add(w)
                             if w == v:
                                 break
                         yield component
@@ -109,25 +109,48 @@
     """Find roots, i.e. a minimal set of vertices such that all other
     vertices are reachable from them."""
 
-    roots = set()
-    notseen = set(vertices)     # set of vertices that are not reachable yet
-                                # from any vertex in 'roots'
-    def addroot(root):
-        roots.add(root)
-        if root in notseen:
-            notseen.remove(root)
-        for v in vertices_reachable_from(root, notseen.union(roots), edges):
-            if v is not root:
-                if v in roots:
-                    roots.remove(v)   # this older root is no longer needed
-                else:
-                    notseen.remove(v)
+    rep = {}    # maps all vertices to a random representing vertex
+                # from the same strongly connected component
+    for component in strong_components(vertices, edges):
+        random_vertex = component.pop()
+        rep[random_vertex] = random_vertex
+        for v in component:
+            rep[v] = random_vertex
+
+    roots = set(rep.values())
+    for v in vertices:
+        v1 = rep[v]
+        for edge in edges[v]:
+            try:
+                v2 = rep[edge.target]
+                if v1 is not v2:      # cross-component edge: no root is needed
+                    roots.remove(v2)  # in the target component
+            except KeyError:
+                pass
 
-    while notseen:
-        addroot(notseen.pop())
     return roots
 
 
+def compute_depths(roots, vertices, edges):
+    """The 'depth' of a vertex is its minimal distance from any root."""
+    depths = {}
+    curdepth = 0
+    for v in roots:
+        depths[v] = 0
+    pending = list(roots)
+    while pending:
+        curdepth += 1
+        prev_generation = pending
+        pending = []
+        for v in prev_generation:
+            for edge in edges[v]:
+                v2 = edge.target
+                if v2 in vertices and v2 not in depths:
+                    depths[v2] = curdepth
+                    pending.append(v2)
+    return depths
+
+
 def is_acyclic(vertices, edges):
     class CycleFound(Exception):
         pass
@@ -209,6 +232,70 @@
     assert is_acyclic(vertices, remaining_edges)
 
 
+def break_cycles_v(vertices, edges):
+    """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.
+
+    remaining_vertices = vertices.copy()
+    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,))
+        progress = False
+        for root in roots:
+            if root in roots_finished:
+                continue
+            cycles = all_cycles(root, remaining_vertices, edges)
+            if not cycles:
+                roots_finished.add(root)
+                continue
+            #print 'from root %r: %d cycles' % (root, len(cycles))
+            allcycles = {}
+            v2cycles = {}
+            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)
+
+
 def show_graph(vertices, edges):
     from pypy.translator.tool.graphpage import GraphPage, DotGen
     class MathGraphPage(GraphPage):

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	Thu Jun 19 14:23:27 2008
@@ -57,11 +57,30 @@
                 edges['B'][1] in result or
                 edges['E'][0] in result)
 
+    def test_break_cycles_v(self):
+        edges = copy_edges(self.edges)
+        edges['R'] = [Edge('R', 'B')]
+        saved = copy_edges(edges)
+        result = list(break_cycles_v(edges, edges))
+        assert edges == saved
+        assert len(result) == 2
+        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
+        # from the root 'R'.
+
     def test_find_roots(self):
         roots = list(find_roots(self.edges, self.edges))
         roots.sort()
         assert ''.join(roots) in ('AG', 'BG', 'EG')
 
+        edges = copy_edges(self.edges)
+        edges['R'] = [Edge('R', 'B')]
+        roots = list(find_roots(edges, edges))
+        roots.sort()
+        assert ''.join(roots) == 'GR'
+
 
 class TestLoops:
     # a graph with 20 loops of length 10 each, plus an edge from each loop to
@@ -80,10 +99,14 @@
         edges = self.edges
         result = list(strong_components(self.vertices, edges))
         assert len(result) == 20
-        result.sort()
-        for i in range(20):
-            comp = list(result[i])
+        result2 = []
+        for comp in result:
+            comp = list(comp)
             comp.sort()
+            result2.append(comp)
+        result2.sort()
+        for i in range(20):
+            comp = result2[i]
             assert comp == range(i*10, (i+1)*10)
 
     def test_break_cycles(self, edges=None):
@@ -107,6 +130,12 @@
         v = list(roots)[0]
         assert v in range(10)
 
+    def test_find_roots_2(self):
+        edges = copy_edges(self.edges)
+        edges[190].append(Edge(190, 5))
+        roots = find_roots(self.vertices, edges)
+        assert len(roots) == 1
+
 
 class TestTree:
     edges = make_edge_dict([Edge(i//2, i) for i in range(1, 52)])
@@ -192,3 +221,19 @@
         expected = self.edges.keys()
         expected.sort()
         assert vertices == expected
+
+    def test_break_cycles(self):
+        list(break_cycles(self.edges, self.edges))
+        # assert is_acyclic(): included in break_cycles() itself
+
+    def test_break_cycles_v(self):
+        list(break_cycles_v(self.edges, self.edges))
+        # assert is_acyclic(): included in break_cycles_v() itself
+
+    def test_find_roots(self):
+        roots = find_roots(self.edges, self.edges)
+        reachable = set()
+        for root in roots:
+            reachable |= set(vertices_reachable_from(root, self.edges,
+                                                     self.edges))
+        assert reachable == set(self.edges)

Modified: pypy/dist/pypy/translator/transform.py
==============================================================================
--- pypy/dist/pypy/translator/transform.py	(original)
+++ pypy/dist/pypy/translator/transform.py	Thu Jun 19 14:23:27 2008
@@ -240,30 +240,29 @@
 def insert_ll_stackcheck(translator):
     from pypy.translator.backendopt.support import find_calls_from
     from pypy.rlib.rstack import stack_check
-    from pypy.tool.algo.graphlib import Edge, make_edge_dict, break_cycles
+    from pypy.tool.algo.graphlib import Edge, make_edge_dict, break_cycles_v
     rtyper = translator.rtyper
     graph = rtyper.annotate_helper(stack_check, [])
     rtyper.specialize_more_blocks()
     stack_check_ptr = rtyper.getcallable(graph)
     stack_check_ptr_const = Constant(stack_check_ptr, lltype.typeOf(stack_check_ptr))
-    edges = []
-    graphs_to_patch = {}
-    insert_in = {}
+    edges = set()
+    insert_in = set()
     for caller in translator.graphs:
         for block, callee in find_calls_from(translator, caller):
             if getattr(getattr(callee, 'func', None),
                        'insert_stack_check_here', False):
-                insert_in[callee.startblock] = True
+                insert_in.add(callee.startblock)
                 continue
-            edge = Edge(caller, callee)
-            edge.block = block
-            edges.append(edge)
+            if block is not caller.startblock:
+                edges.add((caller.startblock, block))
+            edges.add((block, callee.startblock))
+
+    edgelist = [Edge(block1, block2) for (block1, block2) in edges]
+    edgedict = make_edge_dict(edgelist)
+    for block in break_cycles_v(edgedict, edgedict):
+        insert_in.add(block)
 
-    edgedict = make_edge_dict(edges)
-    for edge in break_cycles(edgedict, edgedict):
-        block = edge.block
-        insert_in[block] = True
-        
     for block in insert_in:
         v = Variable()
         v.concretetype = lltype.Void



More information about the Pypy-commit mailing list