[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