[pypy-commit] pypy value-profiling: for local variables, only track the type to make the interpreter less bad

cfbolz noreply at buildbot.pypy.org
Tue Aug 18 12:07:48 CEST 2015


Author: Carl Friedrich Bolz <cfbolz at gmx.de>
Branch: value-profiling
Changeset: r79032:4c1c11cfc6a9
Date: 2015-08-18 11:43 +0200
http://bitbucket.org/pypy/pypy/changeset/4c1c11cfc6a9/

Log:	for local variables, only track the type to make the interpreter
	less bad

diff --git a/pypy/interpreter/pycode.py b/pypy/interpreter/pycode.py
--- a/pypy/interpreter/pycode.py
+++ b/pypy/interpreter/pycode.py
@@ -50,22 +50,15 @@
     kwargname = varnames[argcount] if code.co_flags & CO_VARKEYWORDS else None
     return Signature(argnames, varargname, kwargname)
 
-class ValueProf(valueprof.ValueProf):
-    def is_int(self, w_obj):
-        from pypy.objspace.std.intobject import W_IntObject
-        return type(w_obj) is W_IntObject
-
-    def get_int_val(self, w_obj):
-        from pypy.objspace.std.intobject import W_IntObject
-        assert isinstance(w_obj, W_IntObject)
-        return w_obj.intval
+class KnownTypesVersion(object):
+    pass
 
 class PyCode(eval.Code):
     "CPython-style code objects."
     _immutable_ = True
     _immutable_fields_ = ["co_consts_w[*]", "co_names_w[*]", "co_varnames[*]",
                           "co_freevars[*]", "co_cellvars[*]",
-                          "_args_as_cellvars[*]", "vprofs[*]"]
+                          "_args_as_cellvars[*]", "_known_types_version?"]
 
     def __init__(self, space,  argcount, nlocals, stacksize, flags,
                      code, consts, names, varnames, filename,
@@ -95,7 +88,13 @@
         self._signature = cpython_code_signature(self)
         self._initialize()
         self._init_ready()
-        self.vprofs = [ValueProf('%s %s' % (self.co_name, self.co_varnames[i])) for i in range(self.co_nlocals)]
+        # a list of either None, W_Root, or a subclass thereof
+        # None means "have not seen a value in that local variable yet
+        # W_Root can be anything
+        # otherwise it's the precise class of *all* values ever stored in that
+        # local
+        self._known_types = [None] * self.co_nlocals
+        self._known_types_version = KnownTypesVersion()
 
     def _initialize(self):
         if self.co_cellvars:
@@ -140,6 +139,21 @@
     def _init_ready(self):
         "This is a hook for the vmprof module, which overrides this method."
 
+    def _get_known_type(self, varindex):
+        # somewhat subtle:
+        if not jit.we_are_jitted():
+            return self._known_types[varindex]
+        return self._get_known_type_elidable(varindex, self._known_types_version)
+
+    @jit.elidable
+    def _get_known_type_elidable(self, varindex, version):
+        assert version is self._known_types_version
+        return self._known_types[varindex]
+
+    def _update_known_type(self, varindex, cls):
+        self._known_types[varindex] = cls
+        self._known_types_version = KnownTypesVersion()
+
     def _cleanup_(self):
         if (self.magic == cpython_magic and
             '__pypy__' not in sys.builtin_module_names):
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -4,7 +4,7 @@
 from rpython.rlib import jit
 from rpython.rlib.debug import make_sure_not_resized, check_nonneg
 from rpython.rlib.jit import hint, we_are_jitted
-from rpython.rlib.objectmodel import we_are_translated, instantiate
+from rpython.rlib.objectmodel import we_are_translated, instantiate, specialize
 from rpython.rlib.rarithmetic import intmask, r_uint
 from rpython.tool.pairtype import extendabletype
 
@@ -146,36 +146,38 @@
         return cell
 
     def _getlocal(self, varindex):
-        from pypy.objspace.std.intobject import W_IntObject
-        # some careful logic there
-        if we_are_jitted():
-            vprof = self.getcode().vprofs[varindex]
-            if vprof.can_fold_read_int():
-                return W_IntObject(vprof.read_constant_int())
-            elif vprof.can_fold_read_obj():
-                w_res = vprof.try_read_constant_obj()
-                if w_res is not None:
-                    return w_res
         w_res = self.locals_cells_stack_w[varindex]
         if we_are_jitted():
-            vprof = self.getcode().vprofs[varindex]
-            if vprof.class_is_known():
-                jit.record_exact_class(w_res, vprof.read_constant_cls())
+            cls = self.getcode()._get_known_type(varindex)
+            if cls is not None and cls is not W_Root:
+                jit.record_exact_class(w_res, cls)
         return w_res
 
-    def _setlocal(self, varindex, value):
-        self._value_profile_local(varindex, value)
+    @specialize.arg(3)
+    def _setlocal(self, varindex, value, can_be_None=True):
+        self._see_write(varindex, value, can_be_None)
         self.locals_cells_stack_w[varindex] = value
 
-    def _value_profile_local(self, varindex, value):
-        from pypy.objspace.std.intobject import W_IntObject
-        vprof = self.getcode().vprofs[varindex]
-        vprof.see_write(value)
+    @specialize.arg(3)
+    def _see_write(self, varindex, value, can_be_None=True):
+        cls = self.getcode()._get_known_type(varindex)
+        if cls is W_Root:
+            return
+        if can_be_None and value is None:
+            new_cls = W_Root
+        else:
+            new_cls = value.__class__
+            if cls is not None:
+                if new_cls is not cls:
+                    new_cls = W_Root
+                else:
+                    return
+        self.getcode()._update_known_type(varindex, new_cls)
 
     @jit.unroll_safe
     def _all_locals_changed(self):
-        for i, vprof in enumerate(self.getcode().vprofs):
-            vprof.see_write(self.locals_cells_stack_w[i])
+        for i in range(self.getcode().co_nlocals):
+            self._see_write(i, self.locals_cells_stack_w[i])
 
     def mark_as_escaped(self):
         """
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -505,7 +505,7 @@
     def STORE_FAST(self, varindex, next_instr):
         w_newvalue = self.popvalue()
         assert w_newvalue is not None
-        self._setlocal(varindex, w_newvalue)
+        self._setlocal(varindex, w_newvalue, can_be_None=False)
 
     def getfreevarname(self, index):
         freevarnames = self.pycode.co_cellvars + self.pycode.co_freevars
diff --git a/pypy/interpreter/test/test_pyframe.py b/pypy/interpreter/test/test_pyframe.py
--- a/pypy/interpreter/test/test_pyframe.py
+++ b/pypy/interpreter/test/test_pyframe.py
@@ -537,15 +537,29 @@
         sys.settrace(None)
         assert res == 10
 
-class TestValueProf(object):
-    def test_argument_is_constant(self):
+class TestLocalTypeProfiling(object):
+    def test_argument_is_constant_type(self):
+        from pypy.objspace.std.intobject import W_IntObject
+        from pypy.interpreter.baseobjspace import W_Root
         space = self.space
         w_f = space.appexec([], """():
             def f(x):
                 y = x + 1
             return f""")
+        v1 = w_f.code._known_types_version
+        assert w_f.code._known_types == [None] * 2
         space.call_function(w_f, space.wrap(1))
-        assert len(w_f.code.vprofs) == 2
-        assert w_f.code.vprofs[0].can_fold_read_int()
-        assert w_f.code.vprofs[1].can_fold_read_int()
+        v2 = w_f.code._known_types_version
+        assert v2 is not v1
+        assert w_f.code._known_types == [W_IntObject] * 2
 
+        space.call_function(w_f, space.wrap(2))
+        v3 = w_f.code._known_types_version
+        assert v3 is v2
+        assert w_f.code._known_types == [W_IntObject] * 2
+
+        space.call_function(w_f, space.wrap(1.1))
+        v4 = w_f.code._known_types_version
+        assert v4 is not v3
+        assert w_f.code._known_types == [W_Root] * 2
+


More information about the pypy-commit mailing list