[pypy-commit] pypy default: merge counter-decay again: simplified version, just requiring 2%
arigo
noreply at buildbot.pypy.org
Tue Dec 20 19:09:53 CET 2011
Author: Armin Rigo <arigo at tunes.org>
Branch:
Changeset: r50767:a21cab6475c1
Date: 2011-12-20 19:09 +0100
http://bitbucket.org/pypy/pypy/changeset/a21cab6475c1/
Log: merge counter-decay again: simplified version, just requiring 2%
extra counts on loops if a piece of assembler has been produced in
the meantime. See included explanations for motivation.
diff --git a/pypy/jit/metainterp/test/test_warmstate.py b/pypy/jit/metainterp/test/test_warmstate.py
--- a/pypy/jit/metainterp/test/test_warmstate.py
+++ b/pypy/jit/metainterp/test/test_warmstate.py
@@ -275,3 +275,52 @@
state.make_jitdriver_callbacks()
res = state.can_never_inline(5, 42.5)
assert res is True
+
+def test_cleanup_jitcell_dict():
+ class FakeJitDriverSD:
+ _green_args_spec = [lltype.Signed]
+ #
+ # Test creating tons of jitcells that remain at 0
+ warmstate = WarmEnterState(None, FakeJitDriverSD())
+ get_jitcell = warmstate._make_jitcell_getter_default()
+ cell1 = get_jitcell(True, -1)
+ assert len(warmstate._jitcell_dict) == 1
+ #
+ for i in range(1, 20005):
+ get_jitcell(True, i) # should trigger a clean-up at 20001
+ assert len(warmstate._jitcell_dict) == (i % 20000) + 1
+ #
+ # Same test, with one jitcell that has a counter of BASE instead of 0
+ warmstate = WarmEnterState(None, FakeJitDriverSD())
+ get_jitcell = warmstate._make_jitcell_getter_default()
+ cell2 = get_jitcell(True, -2)
+ cell2.counter = BASE = warmstate.THRESHOLD_LIMIT // 2 # 50%
+ #
+ for i in range(0, 20005):
+ get_jitcell(True, i)
+ assert len(warmstate._jitcell_dict) == (i % 19999) + 2
+ #
+ assert cell2 in warmstate._jitcell_dict.values()
+ assert cell2.counter == int(BASE * 0.92) # decayed once
+ #
+ # Same test, with jitcells that are compiled and freed by the memmgr
+ warmstate = WarmEnterState(None, FakeJitDriverSD())
+ get_jitcell = warmstate._make_jitcell_getter_default()
+ get_jitcell(True, -1)
+ #
+ for i in range(1, 20005):
+ cell = get_jitcell(True, i)
+ cell.counter = -1
+ cell.wref_procedure_token = None # or a dead weakref, equivalently
+ assert len(warmstate._jitcell_dict) == (i % 20000) + 1
+ #
+ # Same test, with counter == -2 (rare case, kept alive)
+ warmstate = WarmEnterState(None, FakeJitDriverSD())
+ get_jitcell = warmstate._make_jitcell_getter_default()
+ cell = get_jitcell(True, -1)
+ cell.counter = -2
+ #
+ for i in range(1, 20005):
+ cell = get_jitcell(True, i)
+ cell.counter = -2
+ assert len(warmstate._jitcell_dict) == i + 1
diff --git a/pypy/jit/metainterp/warmstate.py b/pypy/jit/metainterp/warmstate.py
--- a/pypy/jit/metainterp/warmstate.py
+++ b/pypy/jit/metainterp/warmstate.py
@@ -151,6 +151,7 @@
# counter == -2: tracing is currently going on for this cell
counter = 0
dont_trace_here = False
+ extra_delay = chr(0)
wref_procedure_token = None
def get_procedure_token(self):
@@ -172,7 +173,6 @@
class WarmEnterState(object):
THRESHOLD_LIMIT = sys.maxint // 2
- default_jitcell_dict = None
def __init__(self, warmrunnerdesc, jitdriver_sd):
"NOT_RPYTHON"
@@ -316,6 +316,36 @@
#
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
+ #
+ if not confirm_enter_jit(*args):
+ cell.counter = 0
+ return
+ # start tracing
+ from pypy.jit.metainterp.pyjitpl import MetaInterp
+ metainterp = MetaInterp(metainterp_sd, jitdriver_sd)
+ # set counter to -2, to mean "tracing in effect"
+ cell.counter = -2
+ try:
+ metainterp.compile_and_run_once(jitdriver_sd, *args)
+ finally:
+ if cell.counter == -2:
+ cell.counter = 0
+
def maybe_compile_and_run(threshold, *args):
"""Entry point to the JIT. Called at the point with the
can_enter_jit() hint.
@@ -330,19 +360,9 @@
if n <= self.THRESHOLD_LIMIT: # bound not reached
cell.counter = n
return
- if not confirm_enter_jit(*args):
- cell.counter = 0
+ else:
+ bound_reached(cell, *args)
return
- # bound reached; start tracing
- from pypy.jit.metainterp.pyjitpl import MetaInterp
- metainterp = MetaInterp(metainterp_sd, jitdriver_sd)
- # set counter to -2, to mean "tracing in effect"
- cell.counter = -2
- try:
- metainterp.compile_and_run_once(jitdriver_sd, *args)
- finally:
- if cell.counter == -2:
- cell.counter = 0
else:
if cell.counter != -1:
assert cell.counter == -2
@@ -447,12 +467,40 @@
except AttributeError:
pass
#
+ def _cleanup_dict():
+ minimum = self.THRESHOLD_LIMIT // 20 # minimum 5%
+ killme = []
+ for key, cell in jitcell_dict.iteritems():
+ if cell.counter >= 0:
+ cell.counter = int(cell.counter * 0.92)
+ if cell.counter < minimum:
+ killme.append(key)
+ elif (cell.counter == -1
+ and cell.get_procedure_token() is None):
+ killme.append(key)
+ for key in killme:
+ del jitcell_dict[key]
+ #
+ def _maybe_cleanup_dict():
+ # Once in a while, rarely, when too many entries have
+ # been put in the jitdict_dict, we do a cleanup phase:
+ # we decay all counters and kill entries with a too
+ # low counter.
+ self._trigger_automatic_cleanup += 1
+ if self._trigger_automatic_cleanup > 20000:
+ self._trigger_automatic_cleanup = 0
+ _cleanup_dict()
+ #
+ self._trigger_automatic_cleanup = 0
+ self._jitcell_dict = jitcell_dict # for tests
+ #
def get_jitcell(build, *greenargs):
try:
cell = jitcell_dict[greenargs]
except KeyError:
if not build:
return None
+ _maybe_cleanup_dict()
cell = JitCell()
jitcell_dict[greenargs] = cell
return cell
@@ -464,6 +512,10 @@
get_jitcell_at_ptr = self.jitdriver_sd._get_jitcell_at_ptr
set_jitcell_at_ptr = self.jitdriver_sd._set_jitcell_at_ptr
lltohlhack = {}
+ # note that there is no equivalent of _maybe_cleanup_dict()
+ # in the case of custom getters. We assume that the interpreter
+ # stores the JitCells on some objects that can go away by GC,
+ # like the PyCode objects in PyPy.
#
def get_jitcell(build, *greenargs):
fn = support.maybe_on_top_of_llinterp(rtyper, get_jitcell_at_ptr)
diff --git a/pypy/module/pypyjit/test_pypy_c/test_generators.py b/pypy/module/pypyjit/test_pypy_c/test_generators.py
--- a/pypy/module/pypyjit/test_pypy_c/test_generators.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_generators.py
@@ -21,9 +21,9 @@
assert loop.match_by_id("generator", """
i16 = force_token()
p45 = new_with_vtable(ConstClass(W_IntObject))
- setfield_gc(p45, i29, descr=<SignedFieldDescr .*>)
- setarrayitem_gc(p8, 0, p45, descr=<GcPtrArrayDescr>)
- i47 = arraylen_gc(p8, descr=<GcPtrArrayDescr>) # Should be removed by backend
+ setfield_gc(p45, i29, descr=<FieldS .*>)
+ setarrayitem_gc(p8, 0, p45, descr=<ArrayP .>)
+ i47 = arraylen_gc(p8, descr=<ArrayP .>) # Should be removed by backend
jump(..., descr=...)
""")
assert loop.match_by_id("subtract", """
diff --git a/pypy/module/pypyjit/test_pypy_c/test_misc.py b/pypy/module/pypyjit/test_pypy_c/test_misc.py
--- a/pypy/module/pypyjit/test_pypy_c/test_misc.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_misc.py
@@ -46,7 +46,7 @@
r *= n
n -= 1
return r
- log = self.run(fact, [7], threshold=5)
+ log = self.run(fact, [7], threshold=4)
assert log.result == 5040
loop, = log.loops_by_filename(self.filepath)
assert loop.match("""
diff --git a/pypy/module/pypyjit/test_pypy_c/test_string.py b/pypy/module/pypyjit/test_pypy_c/test_string.py
--- a/pypy/module/pypyjit/test_pypy_c/test_string.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_string.py
@@ -55,8 +55,8 @@
i += int(long(string.digits[i % len(string.digits)], 16))
return i
- log = self.run(main, [1000])
- assert log.result == main(1000)
+ log = self.run(main, [1100])
+ assert log.result == main(1100)
loop, = log.loops_by_filename(self.filepath)
assert loop.match("""
i11 = int_lt(i6, i7)
More information about the pypy-commit
mailing list