[pypy-svn] r18688 - pypy/dist/pypy/translator/locality

tismer at codespeak.net tismer at codespeak.net
Sun Oct 16 15:25:13 CEST 2005


Author: tismer
Date: Sun Oct 16 15:25:11 2005
New Revision: 18688

Added:
   pypy/dist/pypy/translator/locality/calltree.py   (contents, props changed)
   pypy/dist/pypy/translator/locality/projection.py   (contents, props changed)
Modified:
   pypy/dist/pypy/translator/locality/simulation.py
Log:
checking in, incomplete so far but quite far.

Added: pypy/dist/pypy/translator/locality/calltree.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/translator/locality/calltree.py	Sun Oct 16 15:25:11 2005
@@ -0,0 +1,66 @@
+"""
+
+CallTree
+
+An approach to do static call analysis in the PyPy backend
+to produce a somewhat locality-of-reference optimized
+ordering of the function objects in the generated source.
+
+In extent to that, it is planned to produce a non-optimized
+binary from instrumented source code, run some sample
+applications and optimize according to transition statistics.
+This step will only be done if the first approach shows any
+improvement.
+
+Sketch of the algorithm:
+------------------------
+In a first pass, we inspect all function nodes for direct_call
+opcodes and record the callees, if they are constants.
+(Variables will later be tried to find out by re-using the
+information in the translator).
+
+We then run a simulation of calls.
+See pypy/translator/locality/simulation.py.
+
+After that, a poly-dimensional model is computed and morphed
+into a one-dimensional ordering.
+See pypy/translator/locality/projection.py.
+"""
+
+from pypy.objspace.flow.model import Variable, Constant
+
+class CallTree:
+    def __init__(self, funcnodes):
+        self.nodes = funcnodes
+        self.graphs2nodes = self._build_graph2nodes()
+        self.calls = {}
+        for node in self.nodes:
+            self.calls[node] = self.find_callees(node)
+
+    def _build_graph2nodes(self):
+        dic = {}
+        for node in self.nodes:
+            dic[node.obj.graph] = node
+        return dic
+
+    def find_callees(self, node):
+        graph = node.obj.graph
+        res = []
+        for block in graph.iterblocks():
+            for op in block.operations:
+                if op.opname == 'direct_call':
+                    fnarg = op.args[0]
+                    if isinstance(fnarg, Constant):
+                        fnptr = fnarg.value
+                        fn = fnptr._obj
+                        graph = fn.graph
+                        try:
+                            callednode = self.graphs2nodes[graph]
+                        except KeyError:
+                            print "No node found for graph %s" % graph.name
+                            continue
+                        else:
+                            res.append(callednode)
+                    else:
+                        print "Node %s calls Variable %s" % (node, fnarg)
+        return res

Added: pypy/dist/pypy/translator/locality/projection.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/translator/locality/projection.py	Sun Oct 16 15:25:11 2005
@@ -0,0 +1,73 @@
+"""
+
+Turning function dependencies into linear order
+-----------------------------------------------
+
+The purpose of this module is to calculate a good linear
+ordering of functions, according to call transition
+statistics.
+
+Every node has some connections to other nodes, expressed
+in terms of transition frequencies. As a starting point,
+one could assign every node its own dimension. All transitions
+would therefore be orthogonal to each other. The resulting
+vector space would be quite huge.
+
+Instead, we use a different approach:
+
+For a node having t transitions, we order the transitions
+by decreasing frequencies. The initial position of each
+node is this t-dimensional vector.
+
+The distance between two nodes along a transition is the
+Euclidean distance of the intersecion of the nodes dimensions.
+The transition frequencies define a weight for each transition.
+The weighted distance between two nodes
+"""
+
+from pypy.translator.locality.simulation import DemoNode, DemoSim
+from math import sqrt
+
+class SpaceNode:
+    def __init__(self, node):
+        self.func = node.func
+        self.name = node.name
+
+    def setup(self, relations, weights):
+        self.relations = relations
+        self.weights = weights
+        self.position = weights[:] # just anything to start with
+
+    def distance(self, other):
+        # using the nice property of zip to give the minimum length
+        dist = 0.0
+        for x1, x2 in zip(self.position, other.position):
+            d = x2 - x1
+            dist += d * d
+        return sqrt(dist)
+
+    def lonelyness(self):
+        # get the sum of weighted distances
+        lonely = 0.0
+        for weight, relative in zip(self.weights, self.relations):
+            lonely += weight * self.distance(relative)
+        return lonely
+
+    def corrvector(self):
+        pass # XXX continue here
+
+class SpaceGraph:
+    def __init__(self, simgraph):
+        mapping = {}
+        for simnode in simgraph.nodes:
+            mapping[simnode] = SpaceNode(simnode)
+        self.nodes = [mapping[simnode] for simnode in simgraph.nodes]
+        for simnode in simgraph.nodes:
+            relations, weights = simnode.get_relations()
+            relations = [mapping[rel] for rel in relations]
+            node = mapping[simnode]
+            node.setup(relations, weights)
+
+if __name__ == '__main__':
+    from pypy.translator.locality.simulation import test
+    g = SpaceGraph(test())

Modified: pypy/dist/pypy/translator/locality/simulation.py
==============================================================================
--- pypy/dist/pypy/translator/locality/simulation.py	(original)
+++ pypy/dist/pypy/translator/locality/simulation.py	Sun Oct 16 15:25:11 2005
@@ -26,8 +26,20 @@
         self.func = func
         self.name = self._get_name(func)
         self.callees = []
+        self._callers = None # computed
         self.calls = 0
 
+    def __repr__(self):
+        return '(%s)' % self.name
+
+    def __cmp__(self, other):
+        if isinstance(other, self.__class__):
+            return cmp(self.name, other.name)
+        return cmp(id(self), id(other))
+
+    def __hash__(self):
+        return id(self)
+
     def _get_name(self, func):
         # to be overridden
         return func.__name__
@@ -49,6 +61,28 @@
     def simulate_call(self, weight=1):
         self.calls += weight
 
+    # calls and returns are symmetric. We provide a callers
+    # interface that is computed on demand.
+
+    def _get_callers(self):
+        if not self.sim._callers_computed:
+            self.sim._compute_callers()
+        return self.callers
+    callers = property(_get_callers)
+
+    def get_relations(self):
+        # get callees and callers with frequency, ordered
+        # by decreasing frequency and then by name.
+        ret = []
+        for node in self.callees:
+            freq = self.sim.transitions[ (self, node) ]
+            ret.append( (-freq, node) )
+        for node in self.callers:
+            freq = self.sim.transitions[ (node, self) ]
+            ret.append( (-freq, node) )
+        ret.sort()
+        freqs, nodes = zip(*ret)
+        return nodes, [-freq for freq in freqs]
 
 class DemoSim:
     def __init__(self, funcnodes, nodefactory=DemoNode):
@@ -67,6 +101,7 @@
                 callee = name2node[name]
                 node.callees.append(callee)
                 self.transitions[ (node, callee) ] = 0
+        self._callers_computed = False
 
     def _find_names_width(self):
         n = 0
@@ -78,6 +113,7 @@
         self.transitions[ (caller, callee) ] += weight
 
     def run(self, reps=1, root=0):
+        self._callers_computed = False
         self.repetitions_per_call = reps
         root = self.nodes[root]
         root.call()
@@ -113,6 +149,7 @@
         # the transitions in a weighted manner.
         # this allows us to handle recursions as well.
         # first, stimulate nodes if no transitions are pending
+        self._callers_computed = False
         if not self.pending:
             if root is not None:
                 startnodes = [self.nodes[root]]
@@ -135,6 +172,17 @@
         while self.pending:
             self.simulate(call_prob)
 
+    def _compute_callers(self):
+        nodes = {}
+        for node in self.nodes:
+            nodes[node] = node
+            node.callers = []
+        returns = [ (callee, caller)
+                    for caller, callee in self.transitions.keys()]
+        returns.sort()
+        for callee, caller in returns:
+            nodes[callee].callers.append(caller)
+
 # sample functions for proof of correctness
 
 def test(debug=False):
@@ -157,6 +205,7 @@
         sim.sim_all(prob)
         state2 = sim.get_state()
         assert state1 == state2
+    return sim
 
 if __name__ == '__main__':
     test()



More information about the Pypy-commit mailing list