Author: Antonio Cuni <anto.c...@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) + + +@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 + + +@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 + + +@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, []) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit