[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