[pypy-svn] r71527 - in pypy/trunk/pypy/jit/metainterp: . test

arigo at codespeak.net arigo at codespeak.net
Sat Feb 27 12:18:31 CET 2010


Author: arigo
Date: Sat Feb 27 12:18:29 2010
New Revision: 71527

Modified:
   pypy/trunk/pypy/jit/metainterp/compile.py
   pypy/trunk/pypy/jit/metainterp/optimizeopt.py
   pypy/trunk/pypy/jit/metainterp/pyjitpl.py
   pypy/trunk/pypy/jit/metainterp/test/test_basic.py
   pypy/trunk/pypy/jit/metainterp/test/test_compile.py
   pypy/trunk/pypy/jit/metainterp/test/test_pyjitpl.py
   pypy/trunk/pypy/jit/metainterp/warmstate.py
Log:
Merge branch/guard-value-counting-2.

For GUARD_VALUE failures, gives one counter per actual value instead of
just a single counter.  This should focus compilation on the most
commonly seen values, and prevent compilation on the rarely seen values.
As an extreme example, the new test in test_basic checks that if the
GUARD_VALUE sees always different values, the failure never gets
compiled.  The same effect in PyPy would be that if a class' VersionTag
changes all the time, then we should not compile, instead of repeatedly
compiling versions of the code that are never reused.



Modified: pypy/trunk/pypy/jit/metainterp/compile.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/compile.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/compile.py	Sat Feb 27 12:18:29 2010
@@ -209,7 +209,9 @@
         raise NotImplementedError
 
 class ResumeGuardDescr(ResumeDescr):
-    counter = 0
+    _counter = 0        # if < 0, there is one counter per value;
+    _counters = None    # they get stored in _counters then.
+
     # this class also gets the following attributes stored by resume.py code
     rd_snapshot = None
     rd_frame_info_list = None
@@ -226,11 +228,40 @@
         guard_op.fail_args = boxes
         self.guard_opnum = guard_op.opnum
 
+    def make_a_counter_per_value(self, guard_value_op):
+        assert guard_value_op.opnum == rop.GUARD_VALUE
+        box = guard_value_op.args[0]
+        try:
+            i = guard_value_op.fail_args.index(box)
+        except ValueError:
+            return     # xxx probably very rare
+        else:
+            self._counter = ~i      # use ~(index_of_guarded_box_in_fail_args)
+
     def handle_fail(self, metainterp_sd):
         from pypy.jit.metainterp.pyjitpl import MetaInterp
         metainterp = MetaInterp(metainterp_sd)
         return metainterp.handle_guard_failure(self)
 
+    def must_compile(self, metainterp_sd, inputargs_and_holes):
+        trace_eagerness = metainterp_sd.state.trace_eagerness
+        if self._counter >= 0:
+            self._counter += 1
+            return self._counter >= trace_eagerness
+        else:
+            box = inputargs_and_holes[~self._counter]
+            if self._counters is None:
+                self._counters = ResumeGuardCounters()
+            counter = self._counters.see(box)
+            return counter >= trace_eagerness
+
+    def reset_counter_from_failure(self, metainterp):
+        if self._counter >= 0:
+            self._counter = 0
+        self._counters = None
+        warmrunnerstate = metainterp.staticdata.state
+        warmrunnerstate.disable_noninlinable_function(metainterp)
+
     def compile_and_attach(self, metainterp, new_loop):
         # We managed to create a bridge.  Attach the new operations
         # to the corrsponding guard_op and compile from there
@@ -240,7 +271,6 @@
         send_bridge_to_backend(metainterp.staticdata, self, inputargs,
                                new_loop.operations)
 
-
     def _clone_if_mutable(self):
         res = self.__class__(self.metainterp_sd, self.original_greenkey)
         # XXX a bit ugly to have to list them all here
@@ -262,9 +292,11 @@
         if all_virtuals is None:
             all_virtuals = []
         metainterp._already_allocated_resume_virtuals = all_virtuals
-        self.counter = -2     # never compile
         return metainterp.handle_guard_failure(self)
 
+    def must_compile(self, metainterp_sd, inputargs_and_holes):
+        return False     # never compile GUARD_NOT_FORCED failures
+
     @staticmethod
     def force_now(cpu, token):
         # Called during a residual call from the assembler, if the code
@@ -316,6 +348,52 @@
         return data
 
 
+class ResumeGuardCounters(object):
+    # Completely custom algorithm for now: keep 5 pairs (box, counter),
+    # and when we need more, we discard the middle pair (middle in the
+    # current value of the counter).  That way, we tend to keep the
+    # boxes with a high counter, but also we avoid always throwing away
+    # the most recently added box.  **THIS ALGO MUST GO AWAY AT SOME POINT**
+
+    def __init__(self):
+        self.counters = [0] * 5
+        self.boxes = [None] * 5
+
+    def see(self, newbox):
+        newbox = newbox.constbox()
+        # find and update an existing counter
+        unused = -1
+        for i in range(5):
+            cnt = self.counters[i]
+            if cnt:
+                if newbox.same_constant(self.boxes[i]):
+                    cnt += 1
+                    self.counters[i] = cnt
+                    return cnt
+            else:
+                unused = i
+        # not found.  Use a previously unused entry, if there is one
+        if unused >= 0:
+            self.counters[unused] = 1
+            self.boxes[unused] = newbox
+            return 1
+        # no unused entry.  Overwrite the middle one.  Computed with indices
+        # a, b, c meaning the highest, second highest, and third highest
+        # entries.
+        a = 0
+        b = c = -1
+        for i in range(1, 5):
+            if self.counters[i] > self.counters[a]:
+                c = b; b = a; a = i
+            elif b < 0 or self.counters[i] > self.counters[b]:
+                c = b; b = i
+            elif c < 0 or self.counters[i] > self.counters[c]:
+                c = i
+        self.counters[c] = 1
+        self.boxes[c] = newbox
+        return 1
+
+
 class ResumeFromInterpDescr(ResumeDescr):
     def __init__(self, original_greenkey, redkey):
         ResumeDescr.__init__(self, original_greenkey)

Modified: pypy/trunk/pypy/jit/metainterp/optimizeopt.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/optimizeopt.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/optimizeopt.py	Sat Feb 27 12:18:29 2010
@@ -557,20 +557,24 @@
             raise compile.GiveUp
         descr.store_final_boxes(op, newboxes)
         #
-        # Hack: turn guard_value(bool) into guard_true/guard_false.
-        # This is done after the operation is emitted, to let
-        # store_final_boxes_in_guard set the guard_opnum field
-        # of the descr to the original rop.GUARD_VALUE.
-        if op.opnum == rop.GUARD_VALUE and op.args[0] in self.bool_boxes:
-            constvalue = op.args[1].getint()
-            if constvalue == 0:
-                opnum = rop.GUARD_FALSE
-            elif constvalue == 1:
-                opnum = rop.GUARD_TRUE
+        if op.opnum == rop.GUARD_VALUE:
+            if op.args[0] in self.bool_boxes:
+                # Hack: turn guard_value(bool) into guard_true/guard_false.
+                # This is done after the operation is emitted, to let
+                # store_final_boxes_in_guard set the guard_opnum field
+                # of the descr to the original rop.GUARD_VALUE.
+                constvalue = op.args[1].getint()
+                if constvalue == 0:
+                    opnum = rop.GUARD_FALSE
+                elif constvalue == 1:
+                    opnum = rop.GUARD_TRUE
+                else:
+                    raise AssertionError("uh?")
+                op.opnum = opnum
+                op.args = [op.args[0]]
             else:
-                raise AssertionError("uh?")
-            op.opnum = opnum
-            op.args = [op.args[0]]
+                # a real GUARD_VALUE.  Make it use one counter per value.
+                descr.make_a_counter_per_value(op)
 
     def optimize_default(self, op):
         if op.is_always_pure():
@@ -662,6 +666,7 @@
             descr = old_guard_op.descr
             assert isinstance(descr, compile.ResumeGuardDescr)
             descr.guard_opnum = rop.GUARD_VALUE
+            descr.make_a_counter_per_value(old_guard_op)
             emit_operation = False
         constbox = op.args[1]
         assert isinstance(constbox, Const)

Modified: pypy/trunk/pypy/jit/metainterp/pyjitpl.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/pyjitpl.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/pyjitpl.py	Sat Feb 27 12:18:29 2010
@@ -1557,13 +1557,7 @@
 
     def handle_guard_failure(self, key):
         assert isinstance(key, compile.ResumeGuardDescr)
-        warmrunnerstate = self.staticdata.state
-        must_compile = warmrunnerstate.must_compile_from_failure(key)
-        if must_compile:
-            debug_start('jit-tracing')
-        else:
-            debug_start('jit-blackhole')
-        self.initialize_state_from_guard_failure(key, must_compile)
+        self.initialize_state_from_guard_failure(key)
         try:
             return self._handle_guard_failure(key)
         finally:
@@ -1590,8 +1584,7 @@
             return self.designate_target_loop(gmp)
         except ContinueRunningNormallyBase:
             if not started_as_blackhole:
-                warmrunnerstate = self.staticdata.state
-                warmrunnerstate.reset_counter_from_failure(key, self)
+                key.reset_counter_from_failure(self)
             raise
 
     def remove_consts_and_duplicates(self, boxes, startindex, endindex,
@@ -1775,15 +1768,20 @@
         self.initialize_virtualizable(original_boxes)
         return original_boxes
 
-    def initialize_state_from_guard_failure(self, resumedescr, must_compile):
+    def initialize_state_from_guard_failure(self, resumedescr):
         # guard failure: rebuild a complete MIFrame stack
         self.in_recursion = -1 # always one portal around
-        inputargs_and_holes = self.cpu.make_boxes_from_latest_values(resumedescr)
+        inputargs_and_holes = self.cpu.make_boxes_from_latest_values(
+                                                                 resumedescr)
+        must_compile = resumedescr.must_compile(self.staticdata,
+                                                inputargs_and_holes)
         if must_compile:
+            debug_start('jit-tracing')
             self.history = history.History()
             self.history.inputargs = [box for box in inputargs_and_holes if box]
             self.staticdata.profiler.start_tracing()
         else:
+            debug_start('jit-blackhole')
             self.staticdata.profiler.start_blackhole()
             self.history = None   # this means that is_blackholing() is true
         self.rebuild_state_after_failure(resumedescr, inputargs_and_holes)

Modified: pypy/trunk/pypy/jit/metainterp/test/test_basic.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/test/test_basic.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/test/test_basic.py	Sat Feb 27 12:18:29 2010
@@ -1301,6 +1301,22 @@
         res = self.interp_operations(f, [-5])
         assert res == -2
 
+    def test_guard_always_changing_value(self):
+        myjitdriver = JitDriver(greens = [], reds = ['x'])
+        class A:
+            pass
+        def f(x):
+            while x > 0:
+                myjitdriver.can_enter_jit(x=x)
+                myjitdriver.jit_merge_point(x=x)
+                a = A()
+                hint(a, promote=True)
+                x -= 1
+        self.meta_interp(f, [50])
+        self.check_loop_count(1)
+        # this checks that the logic triggered by make_a_counter_per_value()
+        # works and prevents generating tons of bridges
+
 
 class TestOOtype(BasicTests, OOJitMixin):
 

Modified: pypy/trunk/pypy/jit/metainterp/test/test_compile.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/test/test_compile.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/test/test_compile.py	Sat Feb 27 12:18:29 2010
@@ -1,7 +1,8 @@
 from pypy.jit.metainterp.history import LoopToken, ConstInt, History, Stats
+from pypy.jit.metainterp.history import BoxInt
 from pypy.jit.metainterp.specnode import NotSpecNode, ConstantSpecNode
 from pypy.jit.metainterp.compile import insert_loop_token, compile_new_loop
-from pypy.jit.metainterp.compile import ResumeGuardDescr
+from pypy.jit.metainterp.compile import ResumeGuardDescr, ResumeGuardCounters
 from pypy.jit.metainterp import optimize, jitprof, typesystem
 from pypy.jit.metainterp.test.oparser import parse
 from pypy.jit.metainterp.test.test_optimizefindnode import LLtypeMixin
@@ -103,3 +104,52 @@
     assert loop_tokens == [loop_token]
     assert len(cpu.seen) == 0
     assert staticdata.globaldata.loopnumbering == 2    
+
+
+def test_resume_guard_counters():
+    rgc = ResumeGuardCounters()
+    # fill in the table
+    for i in range(5):
+        count = rgc.see(BoxInt(100+i))
+        assert count == 1
+        count = rgc.see(BoxInt(100+i))
+        assert count == 2
+        assert rgc.counters == [0] * (4-i) + [2] * (1+i)
+    for i in range(5):
+        count = rgc.see(BoxInt(100+i))
+        assert count == 3
+    # make a distribution:  [5, 4, 7, 6, 3]
+    assert rgc.counters == [3, 3, 3, 3, 3]
+    count = rgc.see(BoxInt(101))
+    assert count == 4
+    count = rgc.see(BoxInt(101))
+    assert count == 5
+    count = rgc.see(BoxInt(101))
+    assert count == 6
+    count = rgc.see(BoxInt(102))
+    assert count == 4
+    count = rgc.see(BoxInt(102))
+    assert count == 5
+    count = rgc.see(BoxInt(102))
+    assert count == 6
+    count = rgc.see(BoxInt(102))
+    assert count == 7
+    count = rgc.see(BoxInt(103))
+    assert count == 4
+    count = rgc.see(BoxInt(104))
+    assert count == 4
+    count = rgc.see(BoxInt(104))
+    assert count == 5
+    assert rgc.counters == [5, 4, 7, 6, 3]
+    # the next new item should throw away 104, as 5 is the middle counter
+    count = rgc.see(BoxInt(190))
+    assert count == 1
+    assert rgc.counters == [1, 4, 7, 6, 3]
+    # the next new item should throw away 103, as 4 is the middle counter
+    count = rgc.see(BoxInt(191))
+    assert count == 1
+    assert rgc.counters == [1, 1, 7, 6, 3]
+    # the next new item should throw away 100, as 3 is the middle counter
+    count = rgc.see(BoxInt(192))
+    assert count == 1
+    assert rgc.counters == [1, 1, 7, 6, 1]

Modified: pypy/trunk/pypy/jit/metainterp/test/test_pyjitpl.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/test/test_pyjitpl.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/test/test_pyjitpl.py	Sat Feb 27 12:18:29 2010
@@ -149,9 +149,10 @@
     metainterp.rebuild_state_after_failure = rebuild_state_after_failure
     
     class FakeResumeDescr:
-        pass
+        def must_compile(self, *args):
+            return True
     resumedescr = FakeResumeDescr()
-    metainterp.initialize_state_from_guard_failure(resumedescr, True)
+    metainterp.initialize_state_from_guard_failure(resumedescr)
 
     inp = metainterp.history.inputargs
     assert len(inp) == 3

Modified: pypy/trunk/pypy/jit/metainterp/warmstate.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/warmstate.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/warmstate.py	Sat Feb 27 12:18:29 2010
@@ -157,14 +157,6 @@
         if self.profiler is not None:
             self.profiler.set_printing(value >= DEBUG_PROFILE)
 
-    def must_compile_from_failure(self, key):
-        key.counter += 1
-        return key.counter >= self.trace_eagerness
-
-    def reset_counter_from_failure(self, key, metainterp):
-        key.counter = 0
-        self.disable_noninlinable_function(metainterp)
-
     def disable_noninlinable_function(self, metainterp):
         greenkey = metainterp.greenkey_of_huge_function
         if greenkey is not None:



More information about the Pypy-commit mailing list