[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