Author: Christian Tismer <tis...@stackless.com> Branch: win64-stage1 Changeset: r53373:c3d418aee581 Date: 2012-03-12 15:53 -0700 http://bitbucket.org/pypy/pypy/changeset/c3d418aee581/
Log: Merge with default 2 months ago diff --git a/pypy/jit/metainterp/memmgr.py b/pypy/jit/metainterp/memmgr.py --- a/pypy/jit/metainterp/memmgr.py +++ b/pypy/jit/metainterp/memmgr.py @@ -1,5 +1,5 @@ import math -from pypy.rlib.rarithmetic import r_int64 +from pypy.rlib.rarithmetic import r_int64, r_uint from pypy.rlib.debug import debug_start, debug_print, debug_stop from pypy.rlib.objectmodel import we_are_translated @@ -21,6 +21,7 @@ # class MemoryManager(object): + NO_NEXT_CHECK = r_int64(2 ** 63 - 1) def __init__(self): self.check_frequency = -1 @@ -36,12 +37,13 @@ # According to my estimates it's about 5e9 years given 1000 loops # per second self.current_generation = r_int64(1) - self.next_check = r_int64(-1) + self.next_check = self.NO_NEXT_CHECK self.alive_loops = {} + self._cleanup_jitcell_dicts = lambda: None def set_max_age(self, max_age, check_frequency=0): if max_age <= 0: - self.next_check = r_int64(-1) + self.next_check = self.NO_NEXT_CHECK else: self.max_age = max_age if check_frequency <= 0: @@ -49,10 +51,11 @@ self.check_frequency = check_frequency self.next_check = self.current_generation + 1 - def next_generation(self): + def next_generation(self, do_cleanups_now=True): self.current_generation += 1 - if self.current_generation == self.next_check: + if do_cleanups_now and self.current_generation >= self.next_check: self._kill_old_loops_now() + self._cleanup_jitcell_dicts() self.next_check = self.current_generation + self.check_frequency def keep_loop_alive(self, looptoken): @@ -81,3 +84,22 @@ # a single one is not enough for all tests :-( rgc.collect(); rgc.collect(); rgc.collect() debug_stop("jit-mem-collect") + + def get_current_generation_uint(self): + """Return the current generation, possibly truncated to a uint. + To use only as an approximation for decaying counters.""" + return r_uint(self.current_generation) + + def record_jitcell_dict(self, callback): + """NOT_RPYTHON. The given jitcell_dict is a dict that needs + occasional clean-ups of old cells. A cell is old if it never + reached the threshold, and its counter decayed to a tiny value.""" + # note that the various jitcell_dicts have different RPython types, + # so we have to make a different function for each one. These + # functions are chained to each other: each calls the previous one. + def cleanup_dict(): + callback() + cleanup_previous() + # + cleanup_previous = self._cleanup_jitcell_dicts + self._cleanup_jitcell_dicts = cleanup_dict diff --git a/pypy/jit/metainterp/test/test_ajit.py b/pypy/jit/metainterp/test/test_ajit.py --- a/pypy/jit/metainterp/test/test_ajit.py +++ b/pypy/jit/metainterp/test/test_ajit.py @@ -2910,6 +2910,27 @@ res = self.meta_interp(f, [32]) assert res == f(32) + def test_decay_counters(self): + myjitdriver = JitDriver(greens = ['m'], reds = ['n']) + def f(m, n): + while n > 0: + myjitdriver.jit_merge_point(m=m, n=n) + n += m + n -= m + n -= 1 + def main(): + f(5, 7) # run 7x with m=5 counter[m=5] = 7 + f(15, 10) # compiles one loop counter[m=5] = 3 (automatic decay) + f(5, 5) # run 5x times with m=5 counter[m=5] = 8 + # + self.meta_interp(main, [], decay_halflife=1, + function_threshold=0, threshold=9, trace_eagerness=99) + self.check_trace_count(1) + # + self.meta_interp(main, [], decay_halflife=1, + function_threshold=0, threshold=8, trace_eagerness=99) + self.check_trace_count(2) + class TestOOtype(BasicTests, OOJitMixin): 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 @@ -1,3 +1,4 @@ +import math from pypy.rpython.test.test_llinterp import interpret from pypy.rpython.lltypesystem import lltype, llmemory, rstr, rffi from pypy.rpython.ootypesystem import ootype @@ -8,7 +9,7 @@ from pypy.jit.metainterp.history import BoxInt, BoxFloat, BoxPtr from pypy.jit.metainterp.history import ConstInt, ConstFloat, ConstPtr from pypy.jit.codewriter import longlong -from pypy.rlib.rarithmetic import r_singlefloat +from pypy.rlib.rarithmetic import r_singlefloat, r_uint def boxfloat(x): return BoxFloat(longlong.getfloatstorage(x)) @@ -275,3 +276,77 @@ state.make_jitdriver_callbacks() res = state.can_never_inline(5, 42.5) assert res is True + +def test_decay_counters(): + cell = JitCell(r_uint(5)) + cell.counter = 100 + cell.adjust_counter(r_uint(5), math.log(0.9)) + assert cell.counter == 100 + cell.adjust_counter(r_uint(6), math.log(0.9)) + assert cell.counter == 90 + cell.adjust_counter(r_uint(9), math.log(0.9)) + assert cell.counter == int(90 * (0.9**3)) + +def test_cleanup_jitcell_dict(): + from pypy.jit.metainterp.memmgr import MemoryManager + class FakeWarmRunnerDesc: + memory_manager = MemoryManager() + class cpu: + pass + class FakeJitDriverSD: + _green_args_spec = [lltype.Signed] + # + # Test creating tons of jitcells that remain at 0 + warmstate = WarmEnterState(FakeWarmRunnerDesc(), FakeJitDriverSD()) + get_jitcell = warmstate._make_jitcell_getter_default() + cell1 = get_jitcell(True, -1) + assert len(warmstate._jitcell_dict) == 1 + assert FakeWarmRunnerDesc.memory_manager.current_generation == 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 + assert FakeWarmRunnerDesc.memory_manager.current_generation == 2 + # + # Same test, with one jitcell that has a counter of BASE instead of 0 + warmstate = WarmEnterState(FakeWarmRunnerDesc(), FakeJitDriverSD()) + warmstate.set_param_decay_halflife(2) + warmstate.set_param_threshold(5) + warmstate.set_param_function_threshold(0) + get_jitcell = warmstate._make_jitcell_getter_default() + cell2 = get_jitcell(True, -2) + cell2.counter = BASE = warmstate.increment_threshold * 3 + # + 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 * math.sqrt(0.5)) # decayed once + assert FakeWarmRunnerDesc.memory_manager.current_generation == 3 + # + # Same test, with jitcells that are compiled and free by the memmgr + warmstate = WarmEnterState(FakeWarmRunnerDesc(), FakeJitDriverSD()) + get_jitcell = warmstate._make_jitcell_getter_default() + get_jitcell(True, -1) + assert FakeWarmRunnerDesc.memory_manager.current_generation == 3 + # + 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 + assert FakeWarmRunnerDesc.memory_manager.current_generation == 4 + # + # Same test, with counter == -2 (rare case, kept alive) + warmstate = WarmEnterState(FakeWarmRunnerDesc(), FakeJitDriverSD()) + get_jitcell = warmstate._make_jitcell_getter_default() + cell = get_jitcell(True, -1) + cell.counter = -2 + assert FakeWarmRunnerDesc.memory_manager.current_generation == 4 + # + for i in range(1, 20005): + cell = get_jitcell(True, i) + cell.counter = -2 + assert len(warmstate._jitcell_dict) == i + 1 + assert FakeWarmRunnerDesc.memory_manager.current_generation == 5 diff --git a/pypy/jit/metainterp/warmspot.py b/pypy/jit/metainterp/warmspot.py --- a/pypy/jit/metainterp/warmspot.py +++ b/pypy/jit/metainterp/warmspot.py @@ -64,9 +64,11 @@ def jittify_and_run(interp, graph, args, repeat=1, graph_and_interp_only=False, backendopt=False, trace_limit=sys.maxint, + threshold=3, trace_eagerness=2, inline=False, loop_longevity=0, retrace_limit=5, - function_threshold=4, - enable_opts=ALL_OPTS_NAMES, max_retrace_guards=15, **kwds): + function_threshold=4, decay_halflife=0, + enable_opts=ALL_OPTS_NAMES, max_retrace_guards=15, + **kwds): from pypy.config.config import ConfigError translator = interp.typer.annotator.translator try: @@ -83,15 +85,16 @@ pass warmrunnerdesc = WarmRunnerDesc(translator, backendopt=backendopt, **kwds) for jd in warmrunnerdesc.jitdrivers_sd: - jd.warmstate.set_param_threshold(3) # for tests + jd.warmstate.set_param_threshold(threshold) jd.warmstate.set_param_function_threshold(function_threshold) - jd.warmstate.set_param_trace_eagerness(2) # for tests + jd.warmstate.set_param_trace_eagerness(trace_eagerness) jd.warmstate.set_param_trace_limit(trace_limit) jd.warmstate.set_param_inlining(inline) jd.warmstate.set_param_loop_longevity(loop_longevity) jd.warmstate.set_param_retrace_limit(retrace_limit) jd.warmstate.set_param_max_retrace_guards(max_retrace_guards) jd.warmstate.set_param_enable_opts(enable_opts) + jd.warmstate.set_param_decay_halflife(decay_halflife) warmrunnerdesc.finish() if graph_and_interp_only: return interp, graph 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 @@ -1,10 +1,10 @@ -import sys, weakref +import sys, weakref, math from pypy.rpython.lltypesystem import lltype, llmemory, rstr, rffi from pypy.rpython.ootypesystem import ootype from pypy.rpython.annlowlevel import hlstr, cast_base_ptr_to_instance from pypy.rpython.annlowlevel import cast_object_to_ptr from pypy.rlib.objectmodel import specialize, we_are_translated, r_dict -from pypy.rlib.rarithmetic import intmask +from pypy.rlib.rarithmetic import intmask, r_uint from pypy.rlib.nonconst import NonConstant from pypy.rlib.unroll import unrolling_iterable from pypy.rlib.jit import PARAMETERS @@ -153,6 +153,25 @@ dont_trace_here = False wref_procedure_token = None + def __init__(self, generation): + # The stored 'counter' value follows an exponential decay model. + # Conceptually after every generation, it decays by getting + # multiplied by a constant <= 1.0. In practice, decaying occurs + # lazily: the following field records the latest seen generation + # number, and adjustment is done by adjust_counter() when needed. + self.latest_generation_seen = generation + + def adjust_counter(self, generation, log_decay_factor): + if generation != self.latest_generation_seen: + # The latest_generation_seen is older than the current generation. + # Adjust by multiplying self.counter N times by decay_factor, i.e. + # by decay_factor ** N, which is equal to exp(log(decay_factor)*N). + assert self.counter >= 0 + N = generation - self.latest_generation_seen + factor = math.exp(log_decay_factor * N) + self.counter = int(self.counter * factor) + self.latest_generation_seen = generation + def get_procedure_token(self): if self.wref_procedure_token is not None: token = self.wref_procedure_token() @@ -172,7 +191,6 @@ class WarmEnterState(object): THRESHOLD_LIMIT = sys.maxint // 2 - default_jitcell_dict = None def __init__(self, warmrunnerdesc, jitdriver_sd): "NOT_RPYTHON" @@ -213,6 +231,17 @@ def set_param_inlining(self, value): self.inlining = value + def set_param_decay_halflife(self, value): + # Use 0 or -1 to mean "no decay". Initialize the internal variable + # 'log_decay_factor'. It is choosen such that by multiplying the + # counter on loops by 'exp(log_decay_factor)' (<= 1.0) every + # generation, then the counter will be divided by two after 'value' + # generations have passed. + if value <= 0: + self.log_decay_factor = 0.0 # log(1.0) + else: + self.log_decay_factor = math.log(0.5) / value + def set_param_enable_opts(self, value): from pypy.jit.metainterp.optimizeopt import ALL_OPTS_DICT, ALL_OPTS_NAMES @@ -282,6 +311,11 @@ confirm_enter_jit = self.confirm_enter_jit range_red_args = unrolling_iterable( range(num_green_args, num_green_args + jitdriver_sd.num_red_args)) + memmgr = self.warmrunnerdesc.memory_manager + if memmgr is not None: + get_current_generation = memmgr.get_current_generation_uint + else: + get_current_generation = lambda: r_uint(0) # get a new specialized copy of the method ARGS = [] for kind in jitdriver_sd.red_args_types: @@ -326,6 +360,8 @@ if cell.counter >= 0: # update the profiling counter + cell.adjust_counter(get_current_generation(), + self.log_decay_factor) n = cell.counter + threshold if n <= self.THRESHOLD_LIMIT: # bound not reached cell.counter = n @@ -418,6 +454,15 @@ # return jit_getter + def _new_jitcell(self): + warmrunnerdesc = self.warmrunnerdesc + if (warmrunnerdesc is not None and + warmrunnerdesc.memory_manager is not None): + gen = warmrunnerdesc.memory_manager.get_current_generation_uint() + else: + gen = r_uint(0) + return JitCell(gen) + def _make_jitcell_getter_default(self): "NOT_RPYTHON" jitdriver_sd = self.jitdriver_sd @@ -447,13 +492,53 @@ except AttributeError: pass # + memmgr = self.warmrunnerdesc and self.warmrunnerdesc.memory_manager + if memmgr: + def _cleanup_dict(): + minimum = sys.maxint + if self.increment_threshold > 0: + minimum = min(minimum, self.increment_threshold) + if self.increment_function_threshold > 0: + minimum = min(minimum, self.increment_function_threshold) + currentgen = memmgr.get_current_generation_uint() + killme = [] + for key, cell in jitcell_dict.iteritems(): + if cell.counter >= 0: + cell.adjust_counter(currentgen, self.log_decay_factor) + 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(): + # If no tracing goes on at all because the jitcells are + # each time for new greenargs, the dictionary grows forever. + # So every one in a (rare) while, we decide to force an + # artificial next_generation() and _cleanup_dict(). + self._trigger_automatic_cleanup += 1 + if self._trigger_automatic_cleanup > 20000: + self._trigger_automatic_cleanup = 0 + memmgr.next_generation(do_cleanups_now=False) + _cleanup_dict() + # + self._trigger_automatic_cleanup = 0 + self._jitcell_dict = jitcell_dict # for tests + memmgr.record_jitcell_dict(_cleanup_dict) + else: + def _maybe_cleanup_dict(): + pass + # def get_jitcell(build, *greenargs): try: cell = jitcell_dict[greenargs] except KeyError: if not build: return None - cell = JitCell() + _maybe_cleanup_dict() + cell = self._new_jitcell() jitcell_dict[greenargs] = cell return cell return get_jitcell @@ -464,6 +549,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 record_jitcell_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) @@ -485,7 +574,7 @@ if not build: return cell if cell is None: - cell = JitCell() + cell = self._new_jitcell() # <hacks> if we_are_translated(): cellref = cast_object_to_ptr(BASEJITCELL, cell) diff --git a/pypy/rlib/jit.py b/pypy/rlib/jit.py --- a/pypy/rlib/jit.py +++ b/pypy/rlib/jit.py @@ -395,6 +395,7 @@ 'retrace_limit': 5, 'max_retrace_guards': 15, 'enable_opts': 'all', + 'decay_halflife': 40, } unroll_parameters = unrolling_iterable(PARAMETERS.items()) DEFAULT = object() diff --git a/pypy/rpython/rint.py b/pypy/rpython/rint.py --- a/pypy/rpython/rint.py +++ b/pypy/rpython/rint.py @@ -127,10 +127,7 @@ rtype_inplace_rshift = rtype_rshift def rtype_pow(_, hop): - raise MissingRTypeOperation("pow(int, int)" - " (use float**float instead; it is too" - " easy to overlook the overflow" - " issues of int**int)") + raise MissingRTypeOperation("'**' not supported in RPython") rtype_pow_ovf = rtype_pow rtype_inplace_pow = rtype_pow _______________________________________________ pypy-commit mailing list pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit