[pypy-commit] pypy win64-stage1: Merge with default 2 months ago

ctismer noreply at buildbot.pypy.org
Mon Mar 12 23:56:27 CET 2012


Author: Christian Tismer <tismer at 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


More information about the pypy-commit mailing list