Commit 0f240448 authored by Stefan Behnel's avatar Stefan Behnel

speed up adding/subtracting constant Python floats

parent e8446db5
......@@ -15,7 +15,7 @@ Features added
* Tracing is supported in ``nogil`` functions/sections and module init code.
* Adding/subtracting small constant Python integers is faster.
* Adding/subtracting constant Python floats and small integers is faster.
Bugs fixed
----------
......
......@@ -2784,54 +2784,72 @@ class OptimizeBuiltinCalls(Visitor.NodeRefCleanupMixin,
PyrexTypes.py_object_type, [
PyrexTypes.CFuncTypeArg("op1", PyrexTypes.py_object_type, None),
PyrexTypes.CFuncTypeArg("op2", PyrexTypes.py_object_type, None),
PyrexTypes.CFuncTypeArg("int_op", PyrexTypes.c_long_type, None),
PyrexTypes.CFuncTypeArg("inplace", PyrexTypes.c_int_type, None),
PyrexTypes.CFuncTypeArg("intval", PyrexTypes.c_long_type, None),
PyrexTypes.CFuncTypeArg("inplace", PyrexTypes.c_bint_type, None),
])
Pyx_PyFloat_BinopInt_func_type = PyrexTypes.CFuncType(
PyrexTypes.py_object_type, [
PyrexTypes.CFuncTypeArg("op1", PyrexTypes.py_object_type, None),
PyrexTypes.CFuncTypeArg("op2", PyrexTypes.py_object_type, None),
PyrexTypes.CFuncTypeArg("fval", PyrexTypes.c_double_type, None),
PyrexTypes.CFuncTypeArg("inplace", PyrexTypes.c_bint_type, None),
])
def _handle_simple_method_object___add__(self, node, function, args, is_unbound_method):
return self._optimise_int_binop('Add', node, function, args, is_unbound_method)
return self._optimise_num_binop('Add', node, function, args, is_unbound_method)
def _handle_simple_method_object___sub__(self, node, function, args, is_unbound_method):
return self._optimise_int_binop('Subtract', node, function, args, is_unbound_method)
return self._optimise_num_binop('Subtract', node, function, args, is_unbound_method)
def _handle_simple_method_float___add__(self, node, function, args, is_unbound_method):
return self._optimise_num_binop('Add', node, function, args, is_unbound_method)
def _handle_simple_method_float___sub__(self, node, function, args, is_unbound_method):
return self._optimise_num_binop('Subtract', node, function, args, is_unbound_method)
def _optimise_int_binop(self, operator, node, function, args, is_unbound_method):
def _optimise_num_binop(self, operator, node, function, args, is_unbound_method):
"""
Optimise '+' / '-' operator for (likely) small integer operations.
Optimise '+' / '-' operator for (likely) float or small integer operations.
"""
if len(args) != 2:
return node
if not node.type.is_pyobject:
return node
# when adding IntNode to something else, assume other operand is also numeric
if isinstance(args[0], ExprNodes.IntNode):
# when adding IntNode/FloatNode to something else, assume other operand is also numeric
num_nodes = (ExprNodes.IntNode, ExprNodes.FloatNode)
if isinstance(args[0], num_nodes):
if args[1].type is not PyrexTypes.py_object_type:
return node
intval = args[0]
arg_order = 'IntObj'
elif isinstance(args[1], ExprNodes.IntNode):
numval = args[0]
arg_order = 'CObj'
elif isinstance(args[1], num_nodes):
if args[0].type is not PyrexTypes.py_object_type:
return node
intval = args[1]
arg_order = 'ObjInt'
numval = args[1]
arg_order = 'ObjC'
else:
return node
if not intval.has_constant_result() or abs(intval.constant_result) > 2**30:
is_float = isinstance(numval, ExprNodes.FloatNode)
if not numval.has_constant_result() or (not is_float and abs(numval.constant_result) > 2**30):
return node
args = list(args)
self._inject_int_default_argument(intval, args, len(args), PyrexTypes.c_long_type, intval.constant_result)
args.append((ExprNodes.FloatNode if is_float else ExprNodes.IntNode)(
numval.pos, value=numval.value, constant_result=numval.constant_result,
type=PyrexTypes.c_double_type if is_float else PyrexTypes.c_long_type))
inplace = node.inplace if isinstance(node, ExprNodes.NumBinopNode) else False
self._inject_int_default_argument(node, args, len(args), PyrexTypes.c_long_type, int(inplace))
args.append(ExprNodes.BoolNode(node.pos, value=inplace, constant_result=inplace))
utility_code = TempitaUtilityCode.load_cached(
"PyIntBinopWithInt", "Optimize.c",
"PyFloatBinop" if is_float else "PyIntBinop", "Optimize.c",
context=dict(op=operator, order=arg_order))
return self._substitute_method_call(
node, function, "__Pyx_PyInt_%s%s" % (operator, arg_order),
self.Pyx_PyInt_BinopInt_func_type,
node, function, "__Pyx_Py%s_%s%s" % ('Float' if is_float else 'Int', operator, arg_order),
self.Pyx_PyFloat_BinopInt_func_type if is_float else self.Pyx_PyInt_BinopInt_func_type,
'__%s__' % operator[:3].lower(), is_unbound_method, args,
may_return_none=True,
with_none_check=False,
......
......@@ -479,18 +479,18 @@ fallback:
}
/////////////// PyIntBinopWithInt.proto ///////////////
/////////////// PyIntBinop.proto ///////////////
static PyObject* __Pyx_PyInt_{{op}}{{order}}(PyObject *op1, PyObject *op2, long intval, int inplace); /*proto*/
/////////////// PyIntBinopWithInt ///////////////
/////////////// PyIntBinop ///////////////
//@requires: TypeConversion.c::PyLongInternals
{{py: pyval, ival = ('op2', 'b') if order == 'IntObj' else ('op1', 'a') }}
{{py: pyval, ival = ('op2', 'b') if order == 'CObj' else ('op1', 'a') }}
static PyObject* __Pyx_PyInt_{{op}}{{order}}(PyObject *op1, PyObject *op2, CYTHON_UNUSED long intval, int inplace) {
#if CYTHON_COMPILING_IN_CPYTHON
const long {{'a' if order == 'IntObj' else 'b'}} = intval;
const long {{'a' if order == 'CObj' else 'b'}} = intval;
#if PY_MAJOR_VERSION < 3
if (likely(PyInt_CheckExact({{pyval}}))) {
......@@ -525,3 +525,42 @@ static PyObject* __Pyx_PyInt_{{op}}{{order}}(PyObject *op1, PyObject *op2, CYTHO
#endif
return (inplace ? PyNumber_InPlace{{op}} : PyNumber_{{op}})(op1, op2);
}
/////////////// PyFloatBinop.proto ///////////////
static PyObject* __Pyx_PyFloat_{{op}}{{order}}(PyObject *op1, PyObject *op2, double floatval, int inplace); /*proto*/
/////////////// PyFloatBinop ///////////////
//@requires: TypeConversion.c::PyLongInternals
{{py: pyval, fval = ('op2', 'b') if order == 'CObj' else ('op1', 'a') }}
static PyObject* __Pyx_PyFloat_{{op}}{{order}}(PyObject *op1, PyObject *op2, CYTHON_UNUSED double floatval, int inplace) {
#if CYTHON_COMPILING_IN_CPYTHON
const double {{'a' if order == 'CObj' else 'b'}} = floatval;
double {{fval}};
if (likely(PyFloat_CheckExact({{pyval}}))) {
{{fval}} = PyFloat_AS_DOUBLE({{pyval}});
} else
#if PY_MAJOR_VERSION < 3
if (likely(PyInt_CheckExact({{pyval}}))) {
{{fval}} = (double) PyInt_AS_LONG({{pyval}});
} else
#endif
#if PY_MAJOR_VERSION >= 3 && CYTHON_USE_PYLONG_INTERNALS
if (likely(PyLong_CheckExact({{pyval}}))) {
switch (Py_SIZE({{pyval}})) {
case -1: {{fval}} = -(double)((PyLongObject*){{pyval}})->ob_digit[0]; break;
case 0: {{fval}} = 0.0; break;
case 1: {{fval}} = (double)((PyLongObject*){{pyval}})->ob_digit[0]; break;
default: {{fval}} = PyLong_AsDouble({{pyval}}); break;
}
} else
#endif
#endif
return (inplace ? PyNumber_InPlace{{op}} : PyNumber_{{op}})(op1, op2);
#if CYTHON_COMPILING_IN_CPYTHON
return PyFloat_FromDouble(a {{ '+' if op == 'Add' else '-' }} b);
#endif
}
......@@ -39,6 +39,25 @@ def add_x_1(x):
return x + 1
@cython.test_fail_if_path_exists('//AddNode')
def add_x_1f(x):
"""
>>> add_x_1f(0)
1.0
>>> add_x_1f(1)
2.0
>>> add_x_1f(-1)
0.0
>>> add_x_1f(1.5)
2.5
>>> add_x_1f(-1.5)
-0.5
>>> try: add_x_1f("abc")
... except TypeError: pass
"""
return x + 1.0
@cython.test_fail_if_path_exists('//AddNode')
def add_x_large(x):
"""
......@@ -87,6 +106,25 @@ def add_1_x(x):
return 1 + x
@cython.test_fail_if_path_exists('//AddNode')
def add_1f_x(x):
"""
>>> add_1f_x(0)
1.0
>>> add_1f_x(1)
2.0
>>> add_1f_x(-1)
0.0
>>> add_1f_x(1.5)
2.5
>>> add_1f_x(-1.5)
-0.5
>>> try: add_1f_x("abc")
... except TypeError: pass
"""
return 1.0 + x
@cython.test_fail_if_path_exists('//AddNode')
def add_large_x(x):
"""
......
......@@ -54,6 +54,25 @@ def sub_x_1(x):
return x - 1
@cython.test_fail_if_path_exists('//SubNode')
def sub_x_1f(x):
"""
>>> sub_x_1f(0)
-1.0
>>> sub_x_1f(1)
0.0
>>> sub_x_1f(-1)
-2.0
>>> sub_x_1f(1.5)
0.5
>>> sub_x_1f(-1.5)
-2.5
>>> try: sub_x_1f("abc")
... except TypeError: pass
"""
return x - 1.0
@cython.test_fail_if_path_exists('//SubNode')
def sub_x_large(x):
"""
......@@ -98,6 +117,25 @@ def sub_1_x(x):
return 1 - x
@cython.test_fail_if_path_exists('//SubNode')
def sub_1f_x(x):
"""
>>> sub_1f_x(0)
1.0
>>> sub_1f_x(-1)
2.0
>>> sub_1f_x(1)
0.0
>>> sub_1f_x(1.5)
-0.5
>>> sub_1f_x(-1.5)
2.5
>>> try: sub_1f_x("abc")
... except TypeError: pass
"""
return 1.0 - x
@cython.test_fail_if_path_exists('//SubNode')
def sub_large_x(x):
"""
......
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