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