Author: Armin Rigo <ar...@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) _______________________________________________ pypy-commit mailing list pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit