Commit 4c86b941 authored by Stefan Behnel's avatar Stefan Behnel

use normal loop for larger numbers of items in tuple unpacking instead of...

use normal loop for larger numbers of items in tuple unpacking instead of unrolled loop to reduce code overhead
parent fc436693
...@@ -4877,6 +4877,7 @@ class SequenceNode(ExprNode): ...@@ -4877,6 +4877,7 @@ class SequenceNode(ExprNode):
special_unpack = (rhs.type is py_object_type special_unpack = (rhs.type is py_object_type
or rhs.type in (tuple_type, list_type) or rhs.type in (tuple_type, list_type)
or not rhs.type.is_builtin_type) or not rhs.type.is_builtin_type)
long_enough_for_a_loop = len(self.unpacked_items) > 3
if special_unpack: if special_unpack:
tuple_check = 'likely(PyTuple_CheckExact(%s))' % rhs.py_result() tuple_check = 'likely(PyTuple_CheckExact(%s))' % rhs.py_result()
list_check = 'PyList_CheckExact(%s)' % rhs.py_result() list_check = 'PyList_CheckExact(%s)' % rhs.py_result()
...@@ -4889,29 +4890,32 @@ class SequenceNode(ExprNode): ...@@ -4889,29 +4890,32 @@ class SequenceNode(ExprNode):
else: else:
sequence_types = ['Tuple', 'List'] sequence_types = ['Tuple', 'List']
sequence_type_test = "(%s) || (%s)" % (tuple_check, list_check) sequence_type_test = "(%s) || (%s)" % (tuple_check, list_check)
code.putln("#if CYTHON_COMPILING_IN_CPYTHON")
code.putln("if (%s) {" % sequence_type_test) code.putln("if (%s) {" % sequence_type_test)
code.putln("PyObject* sequence = %s;" % rhs.py_result()) code.putln("PyObject* sequence = %s;" % rhs.py_result())
if len(sequence_types) == 2: if len(sequence_types) == 2:
code.putln("if (likely(Py%s_CheckExact(sequence))) {" % sequence_types[0]) code.putln("if (likely(Py%s_CheckExact(sequence))) {" % sequence_types[0])
self.generate_special_parallel_unpacking_code(code, sequence_types[0]) self.generate_special_parallel_unpacking_code(
code, sequence_types[0],
use_loop=long_enough_for_a_loop and sequence_types[0] != 'Tuple')
if len(sequence_types) == 2: if len(sequence_types) == 2:
code.putln("} else {") code.putln("} else {")
self.generate_special_parallel_unpacking_code(code, sequence_types[1]) self.generate_special_parallel_unpacking_code(
code, sequence_types[1], use_loop=long_enough_for_a_loop)
code.putln("}") code.putln("}")
for item in self.unpacked_items:
code.put_incref(item.result(), item.ctype())
rhs.generate_disposal_code(code) rhs.generate_disposal_code(code)
code.putln("} else {") code.putln("} else")
else: code.putln("#endif")
code.putln("{")
code.putln("{")
if special_unpack and rhs.type is tuple_type: if special_unpack and rhs.type is tuple_type:
code.globalstate.use_utility_code(tuple_unpacking_error_code) code.globalstate.use_utility_code(tuple_unpacking_error_code)
code.putln("__Pyx_UnpackTupleError(%s, %s);" % ( code.putln("__Pyx_UnpackTupleError(%s, %s);" % (
rhs.py_result(), len(self.args))) rhs.py_result(), len(self.args)))
code.putln(code.error_goto(self.pos)) code.putln(code.error_goto(self.pos))
else: else:
self.generate_generic_parallel_unpacking_code(code, rhs) self.generate_generic_parallel_unpacking_code(
code, rhs, use_loop=long_enough_for_a_loop)
code.putln("}") code.putln("}")
for value_node in self.coerced_unpacked_items: for value_node in self.coerced_unpacked_items:
...@@ -4920,9 +4924,16 @@ class SequenceNode(ExprNode): ...@@ -4920,9 +4924,16 @@ class SequenceNode(ExprNode):
self.args[i].generate_assignment_code( self.args[i].generate_assignment_code(
self.coerced_unpacked_items[i], code) self.coerced_unpacked_items[i], code)
def generate_special_parallel_unpacking_code(self, code, sequence_type): def generate_special_parallel_unpacking_code(self, code, sequence_type, use_loop):
code.globalstate.use_utility_code(raise_need_more_values_to_unpack) code.globalstate.use_utility_code(raise_need_more_values_to_unpack)
code.globalstate.use_utility_code(raise_too_many_values_to_unpack) code.globalstate.use_utility_code(raise_too_many_values_to_unpack)
if use_loop:
# must be at the start of a C block!
code.putln("PyObject** temps[%s] = {%s};" % (
len(self.unpacked_items),
','.join(['&%s' % item.result() for item in self.unpacked_items])))
code.putln("if (unlikely(Py%s_GET_SIZE(sequence) != %d)) {" % ( code.putln("if (unlikely(Py%s_GET_SIZE(sequence) != %d)) {" % (
sequence_type, len(self.args))) sequence_type, len(self.args)))
code.putln("if (Py%s_GET_SIZE(sequence) > %d) __Pyx_RaiseTooManyValuesError(%d);" % ( code.putln("if (Py%s_GET_SIZE(sequence) > %d) __Pyx_RaiseTooManyValuesError(%d);" % (
...@@ -4930,14 +4941,34 @@ class SequenceNode(ExprNode): ...@@ -4930,14 +4941,34 @@ class SequenceNode(ExprNode):
code.putln("else __Pyx_RaiseNeedMoreValuesError(Py%s_GET_SIZE(sequence));" % sequence_type) code.putln("else __Pyx_RaiseNeedMoreValuesError(Py%s_GET_SIZE(sequence));" % sequence_type)
code.putln(code.error_goto(self.pos)) code.putln(code.error_goto(self.pos))
code.putln("}") code.putln("}")
if use_loop:
# shorter code in a loop works better for lists in CPython
counter = code.funcstate.allocate_temp(PyrexTypes.c_py_ssize_t_type, manage_ref=False)
code.putln("for (%s=0; %s < %s; %s++) {" % (
counter, counter, len(self.unpacked_items), counter))
code.putln("PyObject* item = Py%s_GET_ITEM(sequence, %s); *(temps[%s]) = item;" % (
sequence_type, counter, counter))
code.put_incref("item", PyrexTypes.py_object_type)
code.putln("}")
code.funcstate.release_temp(counter)
else:
# unrolling the loop is very fast for tuples in CPython
for i, item in enumerate(self.unpacked_items): for i, item in enumerate(self.unpacked_items):
code.putln("%s = Py%s_GET_ITEM(sequence, %d); " % (item.result(), sequence_type, i)) code.putln("%s = Py%s_GET_ITEM(sequence, %d); " % (item.result(), sequence_type, i))
code.put_incref(item.result(), item.ctype())
def generate_generic_parallel_unpacking_code(self, code, rhs): def generate_generic_parallel_unpacking_code(self, code, rhs, use_loop):
code.globalstate.use_utility_code(iternext_unpacking_end_utility_code) code.globalstate.use_utility_code(iternext_unpacking_end_utility_code)
code.globalstate.use_utility_code(raise_need_more_values_to_unpack) code.globalstate.use_utility_code(raise_need_more_values_to_unpack)
code.globalstate.use_utility_code(UtilityCode.load_cached("IterFinish", "ObjectHandling.c"))
code.putln("Py_ssize_t index = -1;") # must be at the start of a C block! code.putln("Py_ssize_t index = -1;") # must be at the start of a C block!
if use_loop:
code.putln("PyObject** temps[%s] = {%s};" % (
len(self.unpacked_items),
','.join(['&%s' % item.result() for item in self.unpacked_items])))
iterator_temp = code.funcstate.allocate_temp(py_object_type, manage_ref=True) iterator_temp = code.funcstate.allocate_temp(py_object_type, manage_ref=True)
code.putln( code.putln(
"%s = PyObject_GetIter(%s); %s" % ( "%s = PyObject_GetIter(%s); %s" % (
...@@ -4952,32 +4983,37 @@ class SequenceNode(ExprNode): ...@@ -4952,32 +4983,37 @@ class SequenceNode(ExprNode):
iternext_func, iterator_temp)) iternext_func, iterator_temp))
unpacking_error_label = code.new_label('unpacking_failed') unpacking_error_label = code.new_label('unpacking_failed')
code.use_label(unpacking_error_label)
unpack_code = "%s(%s)" % (iternext_func, iterator_temp) unpack_code = "%s(%s)" % (iternext_func, iterator_temp)
for i in range(len(self.args)): if use_loop:
item = self.unpacked_items[i] code.putln("for (index=0; index < %s; index++) {" % len(self.unpacked_items))
code.putln( code.put("PyObject* item = %s; if (unlikely(!item)) " % unpack_code)
"index = %d; %s = %s; if (unlikely(!%s)) goto %s;" % ( code.put_goto(unpacking_error_label)
code.put_gotref("item")
code.putln("*(temps[index]) = item;")
code.putln("}")
else:
for i, item in enumerate(self.unpacked_items):
code.put(
"index = %d; %s = %s; if (unlikely(!%s)) " % (
i, i,
item.result(), item.result(),
typecast(item.ctype(), py_object_type, unpack_code), unpack_code,
item.result(), item.result()))
unpacking_error_label)) code.put_goto(unpacking_error_label)
code.put_gotref(item.py_result()) code.put_gotref(item.py_result())
code.put_error_if_neg(self.pos, "__Pyx_IternextUnpackEndCheck(%s(%s), %d)" % ( code.put_error_if_neg(self.pos, "__Pyx_IternextUnpackEndCheck(%s, %d)" % (
iternext_func, unpack_code,
iterator_temp, len(self.unpacked_items)))
len(self.args)))
code.put_decref_clear(iterator_temp, py_object_type) code.put_decref_clear(iterator_temp, py_object_type)
code.funcstate.release_temp(iterator_temp) code.funcstate.release_temp(iterator_temp)
code.putln("%s = NULL;" % iternext_func)
code.funcstate.release_temp(iternext_func) code.funcstate.release_temp(iternext_func)
unpacking_done_label = code.new_label('unpacking_done') unpacking_done_label = code.new_label('unpacking_done')
code.put_goto(unpacking_done_label) code.put_goto(unpacking_done_label)
code.put_label(unpacking_error_label) code.put_label(unpacking_error_label)
code.put_decref_clear(iterator_temp, py_object_type) code.put_decref_clear(iterator_temp, py_object_type)
code.putln("if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_StopIteration)) PyErr_Clear();") code.putln("if (__Pyx_IterFinish() == 0) __Pyx_RaiseNeedMoreValuesError(index);")
code.putln("if (!PyErr_Occurred()) __Pyx_RaiseNeedMoreValuesError(index);")
code.putln(code.error_goto(self.pos)) code.putln(code.error_goto(self.pos))
code.put_label(unpacking_done_label) code.put_label(unpacking_done_label)
......
...@@ -319,3 +319,54 @@ def failure_while_unpacking(it): ...@@ -319,3 +319,54 @@ def failure_while_unpacking(it):
""" """
a,b,c = it a,b,c = it
return a,b,c return a,b,c
def unpack_many(it):
"""
>>> items = range(1,13)
>>> unpack_many(items)
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
>>> unpack_many(iter(items))
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
>>> unpack_many(list(items))
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
>>> unpack_many(tuple(items))
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
"""
a,b,c,d,e,f,g,h,i,j,k,l = it
return a,b,c,d,e,f,g,h,i,j,k,l
def unpack_many_tuple(tuple it):
"""
>>> items = range(1,13)
>>> unpack_many_tuple(tuple(items))
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
"""
a,b,c,d,e,f,g,h,i,j,k,l = it
return a,b,c,d,e,f,g,h,i,j,k,l
def unpack_many_list(list it):
"""
>>> items = range(1,13)
>>> unpack_many_list(list(items))
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
"""
a,b,c,d,e,f,g,h,i,j,k,l = it
return a,b,c,d,e,f,g,h,i,j,k,l
def unpack_many_int(it):
"""
>>> items = range(1,13)
>>> unpack_many_int(items)
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
>>> unpack_many_int(iter(items))
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
>>> unpack_many_int(list(items))
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
>>> unpack_many_int(tuple(items))
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
"""
cdef int b
cdef long f
cdef Py_ssize_t h
a,b,c,d,e,f,g,h,i,j,k,l = it
return a,b,c,d,e,f,g,h,i,j,k,l
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment