[pypy-commit] pypy jit-counter: in-progress

arigo noreply at buildbot.pypy.org
Wed Oct 30 17:07:23 CET 2013


Author: Armin Rigo <arigo at tunes.org>
Branch: jit-counter
Changeset: r67737:375fd4fb9083
Date: 2013-10-30 17:06 +0100
http://bitbucket.org/pypy/pypy/changeset/375fd4fb9083/

Log:	in-progress

diff --git a/rpython/jit/metainterp/counter.py b/rpython/jit/metainterp/counter.py
new file mode 100644
--- /dev/null
+++ b/rpython/jit/metainterp/counter.py
@@ -0,0 +1,94 @@
+from rpython.rlib.rarithmetic import r_singlefloat
+from rpython.rtyper.lltypesystem import lltype, rffi
+from rpython.translator.tool.cbuild import ExternalCompilationInfo
+
+
+class JitCounter:
+    DEFAULT_SIZE = 4096
+
+    def __init__(self, size=DEFAULT_SIZE):
+        assert size >= 1 and (size & (size - 1)) == 0     # a power of two
+        self.mask = size - 1
+        self.timetable = lltype.malloc(rffi.CArray(rffi.FLOAT), size,
+                                       flavor='raw', zero=True,
+                                       track_allocation=False)
+        self.celltable = [None] * size
+
+    def compute_threshold(self, threshold):
+        """Return the 'increment' value corresponding to the given number."""
+        if threshold <= 0:
+            return 0.0   # no increment, never reach 1.0
+        if threshold < 2:
+            threshold = 2
+        return 1.0 / threshold   # the number is at most 0.5
+
+    def tick(self, hash, increment):
+        hash &= self.mask
+        counter = float(self.timetable[hash]) + increment
+        if counter < 1.0:
+            self.timetable[hash] = r_singlefloat(counter)
+            return False
+        else:
+            return True
+    tick._always_inline_ = True
+
+    def reset(self, hash):
+        hash &= self.mask
+        self.timetable[hash] = r_singlefloat(0.0)
+
+    def lookup_chain(self, hash):
+        hash &= self.mask
+        return self.celltable[hash]
+
+    def cleanup_chain(self, hash):
+        self.install_new_cell(hash, None)
+
+    def install_new_cell(self, hash, newcell):
+        hash &= self.mask
+        cell = self.celltable[hash]
+        keep = newcell
+        while cell is not None:
+            remove_me = cell.should_remove_jitcell()
+            nextcell = cell.next
+            if not remove_me:
+                cell.next = keep
+                keep = cell
+            cell = nextcell
+        self.celltable[hash] = keep
+
+    def set_decay(self, decay):
+        """Set the decay, from 0 (none) to 1000 (max)."""
+        if decay < 0:
+            decay = 0
+        elif decay > 1000:
+            decay = 1000
+        self.decay_by_mult = 1.0 - (decay * 0.001)
+
+    def decay_all_counters(self):
+        # Called during a minor collection by the GC, to gradually decay
+        # counters that didn't reach their maximum.  Thus if a counter
+        # is incremented very slowly, it will never reach the maximum.
+        # This avoids altogether the JIT compilation of rare paths.
+        # We also call this function when any maximum bound is reached,
+        # to avoid sudden bursts of JIT-compilation (the next one will
+        # not reach the maximum bound immmediately after).  This is
+        # important in corner cases where we would suddenly compile more
+        # than one loop because all counters reach the bound at the same
+        # time, but where compiling all but the first one is pointless.
+        size = self.mask + 1
+        pypy__decay_jit_counters(self.timetable, self.decay_by_mult, size)
+
+
+# this function is written directly in C; gcc will optimize it using SSE
+eci = ExternalCompilationInfo(post_include_bits=["""
+static void pypy__decay_jit_counters(float table[], double f1, long size1) {
+    float f = (float)f1;
+    int i, size = (int)size1;
+    for (i=0; i<size; i++)
+        table[i] *= f;
+}
+"""])
+
+pypy__decay_jit_counters = rffi.llexternal(
+    "pypy__decay_jit_counters", [rffi.FLOATP, lltype.Float, lltype.Signed],
+    lltype.Void, compilation_info=eci, _nowrapper=True, sandboxsafe=True)
diff --git a/rpython/jit/metainterp/test/test_counter.py b/rpython/jit/metainterp/test/test_counter.py
new file mode 100644
--- /dev/null
+++ b/rpython/jit/metainterp/test/test_counter.py
@@ -0,0 +1,50 @@
+from rpython.jit.metainterp.counter import JitCounter
+
+
+def test_tick():
+    jc = JitCounter()
+    incr = jc.compute_threshold(4)
+    for i in range(5):
+        r = jc.tick(1234567, incr)
+        assert r is (i >= 3)
+    for i in range(5):
+        r = jc.tick(1234568, incr)
+        s = jc.tick(1234569, incr)
+        assert r is (i >= 3)
+        assert s is (i >= 3)
+    jc.reset(1234568)
+    for i in range(5):
+        r = jc.tick(1234568, incr)
+        s = jc.tick(1234569, incr)
+        assert r is (i >= 3)
+        assert s is True
+
+def test_install_new_chain():
+    class Dead:
+        next = None
+        def should_remove_jitcell(self):
+            return True
+    class Alive:
+        next = None
+        def should_remove_jitcell(self):
+            return False
+    #
+    jc = JitCounter()
+    assert jc.lookup_chain(1234567) is None
+    d1 = Dead()
+    jc.install_new_cell(1234567, d1)
+    assert jc.lookup_chain(1234567) is d1
+    d2 = Dead()
+    jc.install_new_cell(1234567, d2)
+    assert jc.lookup_chain(1234567) is d2
+    assert d2.next is None
+    #
+    d3 = Alive()
+    jc.install_new_cell(1234567, d3)
+    assert jc.lookup_chain(1234567) is d3
+    assert d3.next is None
+    d4 = Alive()
+    jc.install_new_cell(1234567, d4)
+    assert jc.lookup_chain(1234567) is d3
+    assert d3.next is d4
+    assert d4.next is None
diff --git a/rpython/jit/metainterp/warmspot.py b/rpython/jit/metainterp/warmspot.py
--- a/rpython/jit/metainterp/warmspot.py
+++ b/rpython/jit/metainterp/warmspot.py
@@ -205,6 +205,9 @@
         vrefinfo = VirtualRefInfo(self)
         self.codewriter.setup_vrefinfo(vrefinfo)
         #
+        from rpython.jit.metainterp.counter import JitCounter
+        self.jitcounter = JitCounter()
+        #
         self.hooks = policy.jithookiface
         self.make_virtualizable_infos()
         self.make_driverhook_graphs()
diff --git a/rpython/jit/metainterp/warmstate.py b/rpython/jit/metainterp/warmstate.py
--- a/rpython/jit/metainterp/warmstate.py
+++ b/rpython/jit/metainterp/warmstate.py
@@ -124,16 +124,11 @@
         return rffi.cast(lltype.Signed, x)
 
 
-MODE_COUNTING  = '\x00'   # not yet traced, wait till threshold is reached
-MODE_TRACING   = 'T'      # tracing is currently going on for this cell
-MODE_HAVE_PROC = 'P'      # there is an entry bridge for this cell
-
 class JitCell(BaseJitCell):
-    counter = 0    # when THRESHOLD_LIMIT is reached, start tracing
-    mode = MODE_COUNTING
-    dont_trace_here = False
-    extra_delay = chr(0)
+    tracing = False
+    dont_trace_here = chr(0)
     wref_procedure_token = None
+    next = None
 
     def get_procedure_token(self):
         if self.wref_procedure_token is not None:
@@ -149,6 +144,18 @@
         assert token is not None
         return weakref.ref(token)
 
+    def should_remove_jitcell(self):
+        if self.get_procedure_token() is not None:
+            return False    # don't remove JitCells with a procedure_token
+        if self.tracing:
+            return False    # don't remove JitCells that are being traced
+        if ord(self.dont_trace_here) == 0:
+            return True     # no reason to keep this JitCell
+        else:
+            # decrement dont_trace_here; it will eventually reach zero.
+            self.dont_trace_here = chr(ord(self.dont_trace_here) - 1)
+            return False
+
 # ____________________________________________________________
 
 
@@ -172,12 +179,7 @@
             meth(default_value)
 
     def _compute_threshold(self, threshold):
-        if threshold <= 0:
-            return 0 # never reach the THRESHOLD_LIMIT
-        if threshold < 2:
-            threshold = 2
-        return (self.THRESHOLD_LIMIT // threshold) + 1
-        # the number is at least 1, and at most about half THRESHOLD_LIMIT
+        return self.warmrunnerdesc.jitcounter.compute_threshold(threshold)
 
     def set_param_threshold(self, threshold):
         self.increment_threshold = self._compute_threshold(threshold)
@@ -186,11 +188,14 @@
         self.increment_function_threshold = self._compute_threshold(threshold)
 
     def set_param_trace_eagerness(self, value):
-        self.trace_eagerness = value
+        self.increment_trace_eagerness = self._compute_threshold(value)
 
     def set_param_trace_limit(self, value):
         self.trace_limit = value
 
+    def set_param_decay(self, decay):
+        self.warmrunnerdesc.jitcounter.set_decay(decay)
+
     def set_param_inlining(self, value):
         self.inlining = value
 
@@ -232,7 +237,7 @@
 
     def disable_noninlinable_function(self, greenkey):
         cell = self.jit_cell_at_key(greenkey)
-        cell.dont_trace_here = True
+        cell.dont_trace_here = chr(20)
         debug_start("jit-disableinlining")
         loc = self.get_location_str(greenkey)
         debug_print("disabled inlining", loc)
@@ -242,7 +247,6 @@
         cell = self.jit_cell_at_key(greenkey)
         old_token = cell.get_procedure_token()
         cell.set_procedure_token(procedure_token)
-        cell.mode = MODE_HAVE_PROC       # valid procedure bridge attached
         if old_token is not None:
             self.cpu.redirect_call_assembler(old_token, procedure_token)
             # procedure_token is also kept alive by any loop that used
@@ -281,6 +285,7 @@
                 assert 0, kind
         func_execute_token = self.cpu.make_execute_token(*ARGS)
         cpu = self.cpu
+        jitcounter = self.warmrunnerdesc.jitcounter
 
         def execute_assembler(loop_token, *args):
             # Call the backend to run the 'looptoken' with the given
@@ -306,34 +311,20 @@
             assert 0, "should have raised"
 
         def bound_reached(cell, *args):
-            # bound reached, but we do a last check: if it is the first
-            # time we reach the bound, or if another loop or bridge was
-            # compiled since the last time we reached it, then decrease
-            # the counter by a few percents instead.  It should avoid
-            # sudden bursts of JIT-compilation, and also corner cases
-            # where we suddenly compile more than one loop because all
-            # counters reach the bound at the same time, but where
-            # compiling all but the first one is pointless.
-            curgen = warmrunnerdesc.memory_manager.current_generation
-            curgen = chr(intmask(curgen) & 0xFF)    # only use 8 bits
-            if we_are_translated() and curgen != cell.extra_delay:
-                cell.counter = int(self.THRESHOLD_LIMIT * 0.98)
-                cell.extra_delay = curgen
-                return
-            #
+            jitcounter.reset(
             cell.counter = 0
             if not confirm_enter_jit(*args):
                 return
             # start tracing
             from rpython.jit.metainterp.pyjitpl import MetaInterp
             metainterp = MetaInterp(metainterp_sd, jitdriver_sd)
-            cell.mode = MODE_TRACING
+            cell.tracing = True
+            cell.reset_counter()
             try:
                 metainterp.compile_and_run_once(jitdriver_sd, *args)
             finally:
-                if cell.mode == MODE_TRACING:
-                    cell.counter = 0
-                    cell.mode = MODE_COUNTING
+                cell.tracing = False
+                cell.reset_counter()
 
         def maybe_compile_and_run(threshold, *args):
             """Entry point to the JIT.  Called at the point with the
@@ -565,7 +556,7 @@
             if can_never_inline(*greenargs):
                 return False
             cell = jit_getter(False, *greenargs)
-            if cell is not None and cell.dont_trace_here:
+            if cell is not None and ord(cell.dont_trace_here) != 0:
                 return False
             return True
         def can_inline_callable(greenkey):
diff --git a/rpython/rlib/jit.py b/rpython/rlib/jit.py
--- a/rpython/rlib/jit.py
+++ b/rpython/rlib/jit.py
@@ -442,7 +442,7 @@
     'threshold': 'number of times a loop has to run for it to become hot',
     'function_threshold': 'number of times a function must run for it to become traced from start',
     'trace_eagerness': 'number of times a guard has to fail before we start compiling a bridge',
-    'decay': 'decay counters at each minor collection: percentage kept',
+    'decay': 'decay counters at each minor collection (0=none, 1000=max)',
     'trace_limit': 'number of recorded operations before we abort tracing with ABORT_TOO_LONG',
     'inlining': 'inline python functions or not (1/0)',
     'loop_longevity': 'a parameter controlling how long loops will be kept before being freed, an estimate',
@@ -456,7 +456,7 @@
 PARAMETERS = {'threshold': 1039, # just above 1024, prime
               'function_threshold': 1619, # slightly more than one above, also prime
               'trace_eagerness': 200,
-              'decay': 90,
+              'decay': 100,
               'trace_limit': 6000,
               'inlining': 1,
               'loop_longevity': 1000,


More information about the pypy-commit mailing list