[pypy-commit] pypy default: Trying to improve the performance of the GraphAnalyzer in case of recursion: clean up the various caches and consolidate them in a single DependencyTracker class.

arigo noreply at buildbot.pypy.org
Sun Dec 16 00:45:25 CET 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r59451:9f75b268adc1
Date: 2012-12-16 00:45 +0100
http://bitbucket.org/pypy/pypy/changeset/9f75b268adc1/

Log:	Trying to improve the performance of the GraphAnalyzer in case of
	recursion: clean up the various caches and consolidate them in a
	single DependencyTracker class.

diff --git a/pypy/translator/backendopt/graphanalyze.py b/pypy/translator/backendopt/graphanalyze.py
--- a/pypy/translator/backendopt/graphanalyze.py
+++ b/pypy/translator/backendopt/graphanalyze.py
@@ -7,9 +7,7 @@
 
     def __init__(self, translator):
         self.translator = translator
-        self.analyzed_calls = {}
-        self.analyzed_indirect_calls = {}
-        self.recursion_hit = False
+        self._analyzed_calls = {}
 
     # method overridden by subclasses
 
@@ -106,18 +104,10 @@
         return x
 
     def analyze_direct_call(self, graph, seen=None):
-        if graph in self.analyzed_calls:
-            return self.analyzed_calls[graph]
         if seen is None:
-            seen = set([graph])
-            self.recursion_hit = False
-            started_here = True
-        elif graph in seen:
-            self.recursion_hit = True
-            return self.bottom_result()
-        else:
-            started_here = False
-            seen.add(graph)
+            seen = DependencyTracker(self)
+        if not seen.enter(graph):
+            return seen.get_cached_result(graph)
         result = self.bottom_result()
         graphinfo = self.compute_graph_info(graph)
         for block in graph.iterblocks():
@@ -134,26 +124,15 @@
                 result = self.join_two_results(
                         result, self.analyze_link(exit, seen))
             if self.is_top_result(result):
-                self.analyzed_calls[graph] = result
-                return result
-        if not self.recursion_hit or started_here:
-            self.analyzed_calls[graph] = result
+                break
+        seen.leave_with(result)
         return result
 
     def analyze_indirect_call(self, graphs, seen=None):
-        graphs_t = tuple(graphs)
-        try:
-            return self.analyzed_indirect_calls[graphs_t]
-        except KeyError:
-            results = []
-            cache = True
-            for graph in graphs:
-                results.append(self.analyze_direct_call(graph, seen))
-                cache = cache and (graph in self.analyzed_calls)
-            res = self.join_results(results)
-            if cache:
-                self.analyzed_indirect_calls[graphs_t] = res
-            return res
+        results = []
+        for graph in graphs:
+            results.append(self.analyze_direct_call(graph, seen))
+        return self.join_results(results)
 
     def analyze_oosend(self, TYPE, name, seen=None):
         graphs = TYPE._lookup_graphs(name)
@@ -166,6 +145,78 @@
             for block, op in graph.iterblockops():
                 self.analyze(op)
 
+
+class DependencyTracker(object):
+    """This tracks the analysis of cyclic call graphs."""
+
+    # The point is that one analyzer works fine if the question we ask
+    # it is about a single graph, but in the case of recursion, it will
+    # fail if we ask it about multiple graphs.  The purpose of this
+    # class is to fix the cache in GraphAnalyzer._analyzed_calls after
+    # each round, whenever a new set of graphs have been added to it.
+    # It works by assuming that we can simply use 'join_two_results'
+    # in order to do so.
+
+    def __init__(self, analyzer):
+        self.analyzer = analyzer
+        # mapping {graph: result} (shared with GraphAnalyzer._analyzed_calls)
+        self.graph_results = analyzer._analyzed_calls
+        # mapping {graph: set_of_graphs_that_depend_on_it}
+        self.backward_dependencies = {}
+        # the current stack of graphs being analyzed
+        self.current_stack = []
+        # the set of graphs at which recursion occurs
+        self.recursion_points = set()
+
+    def enter(self, graph):
+        if self.current_stack:
+            caller_graph = self.current_stack[-1]
+            # record a dependency between the old graph and the new one,
+            # i.e. going backward: FROM the new graph...
+            deps = self.backward_dependencies.setdefault(graph, set())
+            deps.add(caller_graph)                  # ... TO the caller one.
+        #
+        if graph not in self.graph_results:
+            self.current_stack.append(graph)
+            self.graph_results[graph] = Ellipsis
+            return True
+        else:
+            self.recursion_points.add(graph)
+            return False
+
+    def leave_with(self, result):
+        graph = self.current_stack.pop()
+        assert self.graph_results[graph] is Ellipsis
+        self.graph_results[graph] = result
+        #
+        if not self.current_stack:
+            self._propagate_backward_recursion()
+
+    def get_cached_result(self, graph):
+        result = self.graph_results[graph]
+        if result is Ellipsis:
+            return self.analyzer.bottom_result()
+        return result
+
+    def _propagate_backward_recursion(self):
+        # called at the end of the analysis.  We need to back-propagate
+        # the results to all graphs, starting from the graphs in
+        # 'recursion_points', if any.
+        recpts = self.recursion_points
+        bwdeps = self.backward_dependencies
+        grpres = self.graph_results
+        join_two_res = self.analyzer.join_two_results
+        while recpts:
+            callee_graph = recpts.pop()
+            result = grpres[callee_graph]
+            for caller_graph in bwdeps.get(callee_graph, ()):
+                oldvalue1 = grpres[caller_graph]
+                result1 = join_two_res(result, oldvalue1)
+                if result1 != oldvalue1:
+                    grpres[caller_graph] = result1
+                    recpts.add(caller_graph)
+
+
 class BoolGraphAnalyzer(GraphAnalyzer):
     """generic way to analyze graphs: recursively follow it until the first
     operation is found on which self.analyze_simple_operation returns True"""


More information about the pypy-commit mailing list