[pypy-commit] pypy default: merge the fast_cffi_list_init branch.

antocuni noreply at buildbot.pypy.org
Mon Oct 21 18:07:05 CEST 2013


Author: Antonio Cuni <anto.cuni at gmail.com>
Branch: 
Changeset: r67483:147153568452
Date: 2013-10-21 18:06 +0200
http://bitbucket.org/pypy/pypy/changeset/147153568452/

Log:	merge the fast_cffi_list_init branch.

	This adds special support for converting a list with IntStrategy to
	a cffi long[] array and viceversa, and the same for FloatStrategy
	and double[] array. Such conversions are now done by doing a simple
	memcpy from/to the storage of the lists, and can be exploited by
	serialization libraries such as msgpack to provide a super-fast
	(de)serialization of lists

diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -239,6 +239,18 @@
         # _____ this code is here to support testing only _____
         return self
 
+    def unpackiterable_int(self, space):
+        lst = space.listview_int(self)
+        if lst:
+            return lst[:]
+        return None
+
+    def unpackiterable_float(self, space):
+        lst = space.listview_float(self)
+        if lst:
+            return lst[:]
+        return None
+
 
 class W_InterpIterable(W_Root):
     def __init__(self, space, w_iterable):
@@ -838,6 +850,22 @@
         return self._unpackiterable_known_length_jitlook(w_iterator,
                                                          expected_length)
 
+
+    def unpackiterable_int(self, w_obj):
+        """
+        Return a RPython list of unwrapped ints out of w_obj. The list is
+        guaranteed to be acopy of the actual data contained in w_obj, so you
+        can freely modify it. It might return None if not supported.
+        """
+        return w_obj.unpackiterable_int(self)
+
+    def unpackiterable_float(self, w_obj):
+        """
+        Same as unpackiterable_int, but for floats.
+        """
+        return w_obj.unpackiterable_float(self)
+
+
     def length_hint(self, w_obj, default):
         """Return the length of an object, consulting its __length_hint__
         method if necessary.
@@ -895,6 +923,20 @@
         """
         return None
 
+    def listview_int(self, w_list):
+        """ Return a list of unwrapped int out of a list of int. If the
+        argument is not a list or does not contain only int, return None.
+        May return None anyway.
+        """
+        return None
+
+    def listview_float(self, w_list):
+        """ Return a list of unwrapped float out of a list of float. If the
+        argument is not a list or does not contain only float, return None.
+        May return None anyway.
+        """
+        return None
+
     def view_as_kwargs(self, w_dict):
         """ if w_dict is a kwargs-dict, return two lists, one of unwrapped
         strings and one of wrapped values. otherwise return (None, None)
diff --git a/pypy/module/_cffi_backend/cdataobj.py b/pypy/module/_cffi_backend/cdataobj.py
--- a/pypy/module/_cffi_backend/cdataobj.py
+++ b/pypy/module/_cffi_backend/cdataobj.py
@@ -282,6 +282,12 @@
     def iter(self):
         return self.ctype.iter(self)
 
+    def unpackiterable_int(self, space):
+        return self.ctype.aslist_int(self)
+
+    def unpackiterable_float(self, space):
+        return self.ctype.aslist_float(self)
+
     @specialize.argtype(1)
     def write_raw_signed_data(self, source):
         misc.write_raw_signed_data(self._cdata, source, self.ctype.size)
diff --git a/pypy/module/_cffi_backend/ctypearray.py b/pypy/module/_cffi_backend/ctypearray.py
--- a/pypy/module/_cffi_backend/ctypearray.py
+++ b/pypy/module/_cffi_backend/ctypearray.py
@@ -105,6 +105,26 @@
     def iter(self, cdata):
         return W_CDataIter(self.space, self.ctitem, cdata)
 
+    def aslist_int(self, cdata):
+        from rpython.rlib.rarray import populate_list_from_raw_array
+        if self.ctitem.is_long():
+            res = []
+            buf = rffi.cast(rffi.LONGP, cdata._cdata)
+            length = cdata.get_array_length()
+            populate_list_from_raw_array(res, buf, length)
+            return res
+        return None
+
+    def aslist_float(self, cdata):
+        from rpython.rlib.rarray import populate_list_from_raw_array
+        if self.ctitem.is_double():
+            res = []
+            buf = rffi.cast(rffi.DOUBLEP, cdata._cdata)
+            length = cdata.get_array_length()
+            populate_list_from_raw_array(res, buf, length)
+            return res
+        return None
+
     def get_vararg_type(self):
         return self.ctptr
 
diff --git a/pypy/module/_cffi_backend/ctypeobj.py b/pypy/module/_cffi_backend/ctypeobj.py
--- a/pypy/module/_cffi_backend/ctypeobj.py
+++ b/pypy/module/_cffi_backend/ctypeobj.py
@@ -43,6 +43,12 @@
     def is_unichar_ptr_or_array(self):
         return False
 
+    def is_long(self):
+        return False
+
+    def is_double(self):
+        return False
+
     def newp(self, w_init):
         space = self.space
         raise operationerrfmt(space.w_TypeError,
@@ -163,6 +169,9 @@
                               "cdata '%s' does not support iteration",
                               self.name)
 
+    def unpackiterable_int(self, cdata):
+        return None
+
     def get_vararg_type(self):
         return self
 
diff --git a/pypy/module/_cffi_backend/ctypeprim.py b/pypy/module/_cffi_backend/ctypeprim.py
--- a/pypy/module/_cffi_backend/ctypeprim.py
+++ b/pypy/module/_cffi_backend/ctypeprim.py
@@ -85,7 +85,6 @@
             return self.space.wrap(s)
         return W_CType.string(self, cdataobj, maxlen)
 
-
 class W_CTypePrimitiveCharOrUniChar(W_CTypePrimitive):
     _attrs_ = []
     is_primitive_integer = True
@@ -171,6 +170,9 @@
             self.vmin = r_uint(-1) << (sh - 1)
             self.vrangemax = (r_uint(1) << sh) - 1
 
+    def is_long(self):
+        return self.size == rffi.sizeof(lltype.Signed)
+
     def cast_to_int(self, cdata):
         return self.convert_to_object(cdata)
 
@@ -274,6 +276,9 @@
 class W_CTypePrimitiveFloat(W_CTypePrimitive):
     _attrs_ = []
 
+    def is_double(self):
+        return self.size == rffi.sizeof(lltype.Float)
+
     def cast(self, w_ob):
         space = self.space
         if isinstance(w_ob, cdataobj.W_CData):
diff --git a/pypy/module/_cffi_backend/ctypeptr.py b/pypy/module/_cffi_backend/ctypeptr.py
--- a/pypy/module/_cffi_backend/ctypeptr.py
+++ b/pypy/module/_cffi_backend/ctypeptr.py
@@ -42,6 +42,12 @@
     def is_char_or_unichar_ptr_or_array(self):
         return isinstance(self.ctitem, ctypeprim.W_CTypePrimitiveCharOrUniChar)
 
+    def aslist_int(self, cdata):
+        return None
+
+    def aslist_float(self, cdata):
+        return None
+
     def cast(self, w_ob):
         # cast to a pointer, to a funcptr, or to an array.
         # Note that casting to an array is an extension to the C language,
@@ -58,19 +64,45 @@
             value = rffi.cast(rffi.CCHARP, value)
         return cdataobj.W_CData(space, value, self)
 
+    def _convert_array_from_list_strategy_maybe(self, cdata, w_ob):
+        from rpython.rlib.rarray import copy_list_to_raw_array
+        int_list = self.space.listview_int(w_ob)
+        float_list = self.space.listview_float(w_ob)
+        #
+        if self.ctitem.is_long() and int_list is not None:
+            cdata = rffi.cast(rffi.LONGP, cdata)
+            copy_list_to_raw_array(int_list, cdata)
+            return True
+        #
+        if self.ctitem.is_double() and float_list is not None:
+            cdata = rffi.cast(rffi.DOUBLEP, cdata)
+            copy_list_to_raw_array(float_list, cdata)
+            return True
+        #
+        return False
+
+    def _convert_array_from_listview(self, cdata, w_ob):
+        space = self.space
+        lst_w = space.listview(w_ob)
+        if self.length >= 0 and len(lst_w) > self.length:
+            raise operationerrfmt(space.w_IndexError,
+                "too many initializers for '%s' (got %d)",
+                                  self.name, len(lst_w))
+        ctitem = self.ctitem
+        for i in range(len(lst_w)):
+            ctitem.convert_from_object(cdata, lst_w[i])
+            cdata = rffi.ptradd(cdata, ctitem.size)
+
     def convert_array_from_object(self, cdata, w_ob):
         space = self.space
+        if self._convert_array_from_list_strategy_maybe(cdata, w_ob):
+            # the fast path worked, we are done now
+            return
+        #
+        # continue with the slow path
         if (space.isinstance_w(w_ob, space.w_list) or
             space.isinstance_w(w_ob, space.w_tuple)):
-            lst_w = space.listview(w_ob)
-            if self.length >= 0 and len(lst_w) > self.length:
-                raise operationerrfmt(space.w_IndexError,
-                    "too many initializers for '%s' (got %d)",
-                                      self.name, len(lst_w))
-            ctitem = self.ctitem
-            for i in range(len(lst_w)):
-                ctitem.convert_from_object(cdata, lst_w[i])
-                cdata = rffi.ptradd(cdata, ctitem.size)
+            self._convert_array_from_listview(cdata, w_ob)
         elif (self.can_cast_anything or
               (self.ctitem.is_primitive_integer and
                self.ctitem.size == rffi.sizeof(lltype.Char))):
diff --git a/pypy/module/_cffi_backend/test/test_fastpath.py b/pypy/module/_cffi_backend/test/test_fastpath.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_cffi_backend/test/test_fastpath.py
@@ -0,0 +1,100 @@
+# side-effect: FORMAT_LONGDOUBLE must be built before test_checkmodule()
+from pypy.module._cffi_backend import misc
+from pypy.module._cffi_backend.ctypeptr import W_CTypePtrOrArray
+
+class AppTest_fast_path_from_list(object):
+    spaceconfig = dict(usemodules=('_cffi_backend', 'cStringIO'))
+
+    def setup_method(self, meth):
+        def forbidden(self, *args):
+            assert False, 'The slow path is forbidden'
+        self._original = W_CTypePtrOrArray._convert_array_from_listview.im_func
+        W_CTypePtrOrArray._convert_array_from_listview = forbidden
+
+    def teardown_method(self, meth):
+        W_CTypePtrOrArray._convert_array_from_listview = self._original
+
+    def test_fast_init_from_list(self):
+        import _cffi_backend
+        LONG = _cffi_backend.new_primitive_type('long')
+        P_LONG = _cffi_backend.new_pointer_type(LONG)
+        LONG_ARRAY = _cffi_backend.new_array_type(P_LONG, None)
+        buf = _cffi_backend.newp(LONG_ARRAY, [1, 2, 3])
+        assert buf[0] == 1
+        assert buf[1] == 2
+        assert buf[2] == 3
+
+    def test_fast_init_from_list_float(self):
+        import _cffi_backend
+        DOUBLE = _cffi_backend.new_primitive_type('double')
+        P_DOUBLE = _cffi_backend.new_pointer_type(DOUBLE)
+        DOUBLE_ARRAY = _cffi_backend.new_array_type(P_DOUBLE, None)
+        buf = _cffi_backend.newp(DOUBLE_ARRAY, [1.1, 2.2, 3.3])
+        assert buf[0] == 1.1
+        assert buf[1] == 2.2
+        assert buf[2] == 3.3
+
+
+class AppTest_fast_path_to_list(object):
+    spaceconfig = dict(usemodules=('_cffi_backend', 'cStringIO'))
+
+    def setup_method(self, meth):
+        from pypy.interpreter import gateway
+        from rpython.rlib import rarray
+        #
+        self.count = 0
+        def get_count(*args):
+            return self.space.wrap(self.count)
+        self.w_get_count = self.space.wrap(gateway.interp2app(get_count))
+        #
+        original = rarray.populate_list_from_raw_array
+        def populate_list_from_raw_array(*args):
+            self.count += 1
+            return original(*args)
+        self._original = original
+        rarray.populate_list_from_raw_array = populate_list_from_raw_array
+        #
+        self.w_runappdirect = self.space.wrap(self.runappdirect)
+
+
+    def teardown_method(self, meth):
+        from rpython.rlib import rarray
+        rarray.populate_list_from_raw_array = self._original
+
+    def test_list_int(self):
+        import _cffi_backend
+        LONG = _cffi_backend.new_primitive_type('long')
+        P_LONG = _cffi_backend.new_pointer_type(LONG)
+        LONG_ARRAY = _cffi_backend.new_array_type(P_LONG, 3)
+        buf = _cffi_backend.newp(LONG_ARRAY)
+        buf[0] = 1
+        buf[1] = 2
+        buf[2] = 3
+        lst = list(buf)
+        assert lst == [1, 2, 3]
+        if not self.runappdirect:
+            assert self.get_count() == 1
+
+    def test_TypeError_if_no_length(self):
+        import _cffi_backend
+        LONG = _cffi_backend.new_primitive_type('long')
+        P_LONG = _cffi_backend.new_pointer_type(LONG)
+        LONG_ARRAY = _cffi_backend.new_array_type(P_LONG, 3)
+        buf = _cffi_backend.newp(LONG_ARRAY)
+        pbuf = _cffi_backend.cast(P_LONG, buf)
+        raises(TypeError, "list(pbuf)")
+
+
+    def test_list_float(self):
+        import _cffi_backend
+        DOUBLE = _cffi_backend.new_primitive_type('double')
+        P_DOUBLE = _cffi_backend.new_pointer_type(DOUBLE)
+        DOUBLE_ARRAY = _cffi_backend.new_array_type(P_DOUBLE, 3)
+        buf = _cffi_backend.newp(DOUBLE_ARRAY)
+        buf[0] = 1.1
+        buf[1] = 2.2
+        buf[2] = 3.3
+        lst = list(buf)
+        assert lst == [1.1, 2.2, 3.3]
+        if not self.runappdirect:
+            assert self.get_count() == 1
diff --git a/pypy/objspace/std/listobject.py b/pypy/objspace/std/listobject.py
--- a/pypy/objspace/std/listobject.py
+++ b/pypy/objspace/std/listobject.py
@@ -139,6 +139,8 @@
 
 class W_ListObject(W_Root):
 
+    strategy = None
+
     def __init__(self, space, wrappeditems, sizehint=-1):
         assert isinstance(wrappeditems, list)
         self.space = space
@@ -290,6 +292,11 @@
         """Return the items in the list as unwrapped ints. If the list does not
         use the list strategy, return None."""
         return self.strategy.getitems_int(self)
+
+    def getitems_float(self):
+        """Return the items in the list as unwrapped floats. If the list does not
+        use the list strategy, return None."""
+        return self.strategy.getitems_float(self)
     # ___________________________________________________
 
     def mul(self, times):
@@ -755,6 +762,9 @@
     def getitems_int(self, w_list):
         return None
 
+    def getitems_float(self, w_list):
+        return None
+
     def getstorage_copy(self, w_list):
         raise NotImplementedError
 
@@ -939,11 +949,16 @@
             w_list.__init__(space, w_iterable.getitems_copy())
             return
 
-        intlist = space.listview_int(w_iterable)
+        intlist = space.unpackiterable_int(w_iterable)
         if intlist is not None:
             w_list.strategy = strategy = space.fromcache(IntegerListStrategy)
-            # need to copy because intlist can share with w_iterable
-            w_list.lstorage = strategy.erase(intlist[:])
+            w_list.lstorage = strategy.erase(intlist)
+            return
+
+        floatlist = space.unpackiterable_float(w_iterable)
+        if floatlist is not None:
+            w_list.strategy = strategy = space.fromcache(FloatListStrategy)
+            w_list.lstorage = strategy.erase(floatlist)
             return
 
         strlist = space.listview_str(w_iterable)
@@ -1573,6 +1588,9 @@
         if reverse:
             l.reverse()
 
+    def getitems_float(self, w_list):
+        return self.unerase(w_list.lstorage)
+
 
 class StringListStrategy(ListStrategy):
     import_from_mixin(AbstractUnwrappedStrategy)
diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py
--- a/pypy/objspace/std/objspace.py
+++ b/pypy/objspace/std/objspace.py
@@ -472,6 +472,15 @@
             return w_obj.getitems_int()
         return None
 
+    def listview_float(self, w_obj):
+        if type(w_obj) is W_ListObject:
+            return w_obj.getitems_float()
+        # dict and set don't have FloatStrategy, so we can just ignore them
+        # for now
+        if isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj):
+            return w_obj.getitems_float()
+        return None
+
     def view_as_kwargs(self, w_dict):
         if type(w_dict) is W_DictMultiObject:
             return w_dict.view_as_kwargs()
diff --git a/pypy/objspace/std/test/test_liststrategies.py b/pypy/objspace/std/test/test_liststrategies.py
--- a/pypy/objspace/std/test/test_liststrategies.py
+++ b/pypy/objspace/std/test/test_liststrategies.py
@@ -645,6 +645,20 @@
         w_l = W_ListObject(space, [space.wrap(1), space.wrap(2), space.wrap(3)])
         assert self.space.listview_int(w_l) == [1, 2, 3]
 
+    def test_listview_float_list(self):
+        space = self.space
+        w_l = W_ListObject(space, [space.wrap(1.1), space.wrap(2.2), space.wrap(3.3)])
+        assert self.space.listview_float(w_l) == [1.1, 2.2, 3.3]
+
+    def test_unpackiterable_int_list(self):
+        space = self.space
+        w_l = W_ListObject(space, [space.wrap(1), space.wrap(2), space.wrap(3)])
+        list_orig = self.space.listview_int(w_l)
+        list_copy = self.space.unpackiterable_int(w_l)
+        assert list_orig == list_copy == [1, 2, 3]
+        list_copy[0] = 42
+        assert list_orig == [1, 2, 3]
+
 
 class TestW_ListStrategiesDisabled:
     spaceconfig = {"objspace.std.withliststrategies": False}
diff --git a/rpython/rlib/rarray.py b/rpython/rlib/rarray.py
new file mode 100644
--- /dev/null
+++ b/rpython/rlib/rarray.py
@@ -0,0 +1,75 @@
+from rpython.annotator import model as annmodel
+from rpython.annotator.listdef import ListDef
+from rpython.rlib.objectmodel import specialize
+from rpython.rlib import jit
+from rpython.rtyper.lltypesystem import lltype, llmemory
+from rpython.rtyper.extregistry import ExtRegistryEntry
+from rpython.tool.pairtype import pair
+
+def copy_list_to_raw_array(lst, array):
+    for i, item in enumerate(lst):
+        array[i] = item
+
+def populate_list_from_raw_array(lst, array, length):
+    lst[:] = [array[i] for i in range(length)]
+
+
+
+class Entry(ExtRegistryEntry):
+    _about_ = copy_list_to_raw_array
+
+    def compute_result_annotation(self, *s_args):
+        pass
+
+    def specialize_call(self, hop):
+        hop.exception_cannot_occur()
+        v_list, v_buf = hop.inputargs(*hop.args_r)
+        return hop.gendirectcall(ll_copy_list_to_raw_array, v_list, v_buf)
+
+
+class Entry(ExtRegistryEntry):
+    _about_ = populate_list_from_raw_array
+
+    def compute_result_annotation(self, s_list, s_array, s_length):
+        s_item = annmodel.lltype_to_annotation(s_array.ll_ptrtype.TO.OF)
+        s_newlist = self.bookkeeper.newlist(s_item)
+        s_newlist.listdef.resize()
+        pair(s_list, s_newlist).union()
+
+    def specialize_call(self, hop):
+        v_list, v_buf, v_length = hop.inputargs(*hop.args_r)
+        hop.exception_is_here()
+        return hop.gendirectcall(ll_populate_list_from_raw_array, v_list, v_buf, v_length)
+
+
+ at specialize.ll()
+def get_raw_buf(ptr):
+    ofs = llmemory.itemoffsetof(lltype.typeOf(ptr).TO, 0)
+    return llmemory.cast_ptr_to_adr(ptr) + ofs
+get_raw_buf._always_inline_ = True
+
+
+ at jit.dont_look_inside
+def ll_copy_list_to_raw_array(ll_list, dst_ptr):
+    # this code is delicate: we must ensure that there are no GC operations
+    # around the call to raw_memcopy
+    #
+    ITEM = lltype.typeOf(dst_ptr).TO.OF
+    size = llmemory.sizeof(ITEM) * ll_list.ll_length()
+    # start of no-GC section
+    src_adr = get_raw_buf(ll_list.ll_items())
+    dst_adr = get_raw_buf(dst_ptr)
+    llmemory.raw_memcopy(src_adr, dst_adr, size)
+    # end of no-GC section
+
+
+ at jit.dont_look_inside
+def ll_populate_list_from_raw_array(ll_list, src_ptr, length):
+    ITEM = lltype.typeOf(src_ptr).TO.OF
+    size = llmemory.sizeof(ITEM) * length
+    ll_list._ll_resize(length)
+    # start of no-GC section
+    src_adr = get_raw_buf(src_ptr)
+    dst_adr = get_raw_buf(ll_list.ll_items())
+    llmemory.raw_memcopy(src_adr, dst_adr, size)
+    # end of no-GC section
diff --git a/rpython/rlib/test/test_rarray.py b/rpython/rlib/test/test_rarray.py
new file mode 100644
--- /dev/null
+++ b/rpython/rlib/test/test_rarray.py
@@ -0,0 +1,64 @@
+from rpython.rlib.rarray import copy_list_to_raw_array, populate_list_from_raw_array
+from rpython.rtyper.lltypesystem import lltype, rffi
+from rpython.rtyper.test.tool import BaseRtypingTest
+
+
+
+class TestRArray(BaseRtypingTest):
+
+    def test_copy_list_to_raw_array(self):
+        ARRAY = rffi.CArray(lltype.Signed)
+        buf = lltype.malloc(ARRAY, 4, flavor='raw')
+        lst = [1, 2, 3, 4]
+        copy_list_to_raw_array(lst, buf)
+        for i in range(4):
+            assert buf[i] == i+1
+        lltype.free(buf, flavor='raw')
+        
+
+    def test_copy_list_to_raw_array_rtyped(self):
+        INTARRAY = rffi.CArray(lltype.Signed)
+        FLOATARRAY = rffi.CArray(lltype.Float)
+        def fn():
+            buf = lltype.malloc(INTARRAY, 3, flavor='raw')
+            lst = [1, 2, 3]
+            copy_list_to_raw_array(lst, buf)
+            for i in range(3):
+                assert buf[i] == lst[i]
+            #
+            buf2 = lltype.malloc(FLOATARRAY, 3, flavor='raw')
+            lst = [1.1, 2.2, 3.3]
+            copy_list_to_raw_array(lst, buf2)
+            for i in range(3):
+                assert buf2[i] == lst[i]
+            #
+            lltype.free(buf, flavor='raw')
+            lltype.free(buf2, flavor='raw')
+        self.interpret(fn, [])
+
+    def test_new_list_from_raw_array(self):
+        INTARRAY = rffi.CArray(lltype.Signed)
+        buf = lltype.malloc(INTARRAY, 4, flavor='raw')
+        buf[0] = 1
+        buf[1] = 2
+        buf[2] = 3
+        buf[3] = 4
+        lst = []
+        populate_list_from_raw_array(lst, buf, 4)
+        assert lst == [1, 2, 3, 4]
+        lltype.free(buf, flavor='raw')
+
+    def test_new_list_from_raw_array_rtyped(self):
+        INTARRAY = rffi.CArray(lltype.Signed)
+        def fn():
+            buf = lltype.malloc(INTARRAY, 4, flavor='raw')
+            buf[0] = 1
+            buf[1] = 2
+            buf[2] = 3
+            buf[3] = 4
+            lst = []
+            populate_list_from_raw_array(lst, buf, 4)
+            assert lst == [1, 2, 3, 4]
+            lltype.free(buf, flavor='raw')
+        #
+        self.interpret(fn, [])


More information about the pypy-commit mailing list