Commit e54d6dc1 authored by Stefan Behnel's avatar Stefan Behnel

Speed up 1-argument method calls, especially those to CyFunctions, by avoiding...

Speed up 1-argument method calls, especially those to CyFunctions, by avoiding the creation of a bound method object.
parent 981da8c9
......@@ -5918,44 +5918,38 @@ class PyMethodCallNode(SimpleCallNode):
if not args:
# fastest special case: try to avoid tuple creation
code.putln("if (%s) {" % self_arg)
code.globalstate.use_utility_code(
UtilityCode.load_cached("PyObjectCallNoArg", "ObjectHandling.c"))
code.globalstate.use_utility_code(
UtilityCode.load_cached("PyObjectCallOneArg", "ObjectHandling.c"))
code.putln(
"%s = __Pyx_PyObject_CallOneArg(%s, %s); %s" % (
self.result(),
"%s = (%s) ? __Pyx_PyObject_CallOneArg(%s, %s) : __Pyx_PyObject_CallNoArg(%s);" % (
self.result(), self_arg,
function, self_arg,
code.error_goto_if_null(self.result(), self.pos)))
code.put_decref_clear(self_arg, py_object_type)
function))
code.put_xdecref_clear(self_arg, py_object_type)
code.funcstate.release_temp(self_arg)
code.putln("} else {")
code.putln(code.error_goto_if_null(self.result(), self.pos))
code.put_gotref(self.py_result())
elif len(args) == 1:
# fastest special case: try to avoid tuple creation
code.globalstate.use_utility_code(
UtilityCode.load_cached("PyObjectCallNoArg", "ObjectHandling.c"))
UtilityCode.load_cached("PyObjectCall2Args", "ObjectHandling.c"))
code.globalstate.use_utility_code(
UtilityCode.load_cached("PyObjectCallOneArg", "ObjectHandling.c"))
arg = args[0]
code.putln(
"%s = __Pyx_PyObject_CallNoArg(%s); %s" % (
self.result(),
function,
code.error_goto_if_null(self.result(), self.pos)))
code.putln("}")
"%s = (%s) ? __Pyx_PyObject_Call2Args(%s, %s, %s) : __Pyx_PyObject_CallOneArg(%s, %s);" % (
self.result(), self_arg,
function, self_arg, arg.py_result(),
function, arg.py_result()))
code.put_xdecref_clear(self_arg, py_object_type)
code.funcstate.release_temp(self_arg)
arg.generate_disposal_code(code)
arg.free_temps(code)
code.putln(code.error_goto_if_null(self.result(), self.pos))
code.put_gotref(self.py_result())
else:
if len(args) == 1:
code.putln("if (!%s) {" % self_arg)
code.globalstate.use_utility_code(
UtilityCode.load_cached("PyObjectCallOneArg", "ObjectHandling.c"))
arg = args[0]
code.putln(
"%s = __Pyx_PyObject_CallOneArg(%s, %s); %s" % (
self.result(),
function, arg.py_result(),
code.error_goto_if_null(self.result(), self.pos)))
arg.generate_disposal_code(code)
code.put_gotref(self.py_result())
code.putln("} else {")
arg_offset = 1
else:
arg_offset = arg_offset_cname
code.globalstate.use_utility_code(
UtilityCode.load_cached("PyFunctionFastCall", "ObjectHandling.c"))
code.globalstate.use_utility_code(
......@@ -5973,9 +5967,9 @@ class PyMethodCallNode(SimpleCallNode):
call_prefix,
function,
Naming.quick_temp_cname,
arg_offset,
arg_offset_cname,
len(args),
arg_offset,
arg_offset_cname,
code.error_goto_if_null(self.result(), self.pos)))
code.put_xdecref_clear(self_arg, py_object_type)
code.put_gotref(self.py_result())
......@@ -5987,7 +5981,7 @@ class PyMethodCallNode(SimpleCallNode):
code.putln("{")
args_tuple = code.funcstate.allocate_temp(py_object_type, manage_ref=True)
code.putln("%s = PyTuple_New(%d+%s); %s" % (
args_tuple, len(args), arg_offset,
args_tuple, len(args), arg_offset_cname,
code.error_goto_if_null(args_tuple, self.pos)))
code.put_gotref(args_tuple)
......@@ -6003,7 +5997,7 @@ class PyMethodCallNode(SimpleCallNode):
arg.make_owned_reference(code)
code.put_giveref(arg.py_result())
code.putln("PyTuple_SET_ITEM(%s, %d+%s, %s);" % (
args_tuple, i, arg_offset, arg.py_result()))
args_tuple, i, arg_offset_cname, arg.py_result()))
if len(args) > 1:
code.funcstate.release_temp(arg_offset_cname)
......
......@@ -4710,7 +4710,7 @@ class FinalOptimizePhase(Visitor.EnvTransform, Visitor.NodeRefCleanupMixin):
else "optimize.unpack_method_calls")):
# optimise simple Python methods calls
if isinstance(node.arg_tuple, ExprNodes.TupleNode) and not (
node.arg_tuple.mult_factor or (node.arg_tuple.is_literal and node.arg_tuple.args)):
node.arg_tuple.mult_factor or (node.arg_tuple.is_literal and len(node.arg_tuple.args) > 1)):
# simple call, now exclude calls to objects that are definitely not methods
may_be_a_method = True
if function.type is Builtin.type_type:
......
......@@ -1432,18 +1432,27 @@ static int __Pyx_PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **me
attr = __Pyx_PyObject_GetAttrStr(obj, name);
goto try_unpack;
}
if (unlikely(tp->tp_dict == NULL && PyType_Ready(tp) < 0)) {
if (unlikely(tp->tp_dict == NULL) && unlikely(PyType_Ready(tp) < 0)) {
return 0;
}
descr = _PyType_Lookup(tp, name);
if (likely(descr != NULL)) {
Py_INCREF(descr);
// Repeating the condition below accommodates for MSVC's inability to test macros inside of macro expansions.
#if PY_MAJOR_VERSION >= 3
#ifdef __Pyx_CyFunction_USED
if (likely(PyFunction_Check(descr) || (Py_TYPE(descr) == &PyMethodDescr_Type) || __Pyx_CyFunction_Check(descr)))
#else
if (likely(PyFunction_Check(descr) || (Py_TYPE(descr) == &PyMethodDescr_Type)))
#endif
#else
// "PyMethodDescr_Type" is not part of the C-API in Py2.
#ifdef __Pyx_CyFunction_USED
if (likely(PyFunction_Check(descr) || __Pyx_CyFunction_Check(descr)))
#else
if (likely(PyFunction_Check(descr)))
#endif
#endif
{
meth_found = 1;
......@@ -2119,7 +2128,7 @@ static CYTHON_INLINE PyObject * __Pyx_PyCFunction_FastCall(PyObject *func_obj, P
#endif /* CYTHON_FAST_PYCCALL */
/////////////// PyObjectCall2Args ///////////////
/////////////// PyObjectCall2Args.proto ///////////////
static CYTHON_UNUSED PyObject* __Pyx_PyObject_Call2Args(PyObject* function, PyObject* arg1, PyObject* arg2); /*proto*/
......
......@@ -350,6 +350,13 @@ cdef class TestDecoratedMethods:
"""
return x
def test_calls(self, x):
"""
>>> TestDecoratedMethods().test_calls(2)
25
"""
return self.test(x) + self.test2(x*10)
cdef class TestUnboundMethodCdef:
"""
......@@ -367,3 +374,20 @@ class TestUnboundMethod:
True
"""
def meth(self): pass
cdef class TestOptimisedBuiltinMethod:
"""
>>> obj = TestOptimisedBuiltinMethod()
>>> obj.append(2)
3
>>> obj.call(2)
4
>>> obj.call(3, obj)
5
"""
def append(self, arg):
print(arg+1)
def call(self, arg, obj=None):
(obj or self).append(arg+1) # optimistically optimised => uses fast fallback method call
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