Commit 160949a7 authored by Stefan Behnel's avatar Stefan Behnel

optimise truediv/floordiv for C long sized Python integers and truediv for floats

parent 03f88fb4
......@@ -19,8 +19,8 @@ Features added
* Adding/subtracting constant Python floats and small integers is faster.
* Binary and/or/xor/rshift operations and modulus with small constant Python
integers are faster.
* Binary and/or/xor/rshift operations and division/modulus with small
constant Python integers are faster.
Bugs fixed
----------
......
......@@ -2825,6 +2825,25 @@ class OptimizeBuiltinCalls(Visitor.NodeRefCleanupMixin,
return node
return self._optimise_num_binop('Remainder', node, function, args, is_unbound_method)
def _handle_simple_method_object___floordiv__(self, node, function, args, is_unbound_method):
return self._optimise_num_div('FloorDivide', node, function, args, is_unbound_method)
def _handle_simple_method_object___truediv__(self, node, function, args, is_unbound_method):
return self._optimise_num_div('TrueDivide', node, function, args, is_unbound_method)
def _optimise_num_div(self, operator, node, function, args, is_unbound_method):
if len(args) != 2 or not args[1].has_constant_result() or args[1].constant_result == 0:
return node
if isinstance(args[1], ExprNodes.IntNode):
if not (-2**30 <= args[1].constant_result <= 2**30):
return node
elif isinstance(args[1], ExprNodes.FloatNode):
if not (-2**53 <= args[1].constant_result <= 2**53):
return node
else:
return node
return self._optimise_num_binop(operator, 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)
......@@ -2861,7 +2880,7 @@ class OptimizeBuiltinCalls(Visitor.NodeRefCleanupMixin,
is_float = isinstance(numval, ExprNodes.FloatNode)
if is_float:
if operator not in ('Add', 'Subtract'):
if operator not in ('Add', 'Subtract', 'TrueDivide'):
return node
elif abs(numval.constant_result) > 2**30:
return node
......
......@@ -14,6 +14,7 @@ from . import Nodes
from . import ExprNodes
from . import Errors
from . import DebugFlags
from . import Future
import cython
......@@ -434,7 +435,7 @@ find_special_method_for_binary_operator = {
'>': '__gt__',
'+': '__add__',
'&': '__and__',
'/': '__truediv__',
'/': '__div__',
'//': '__floordiv__',
'<<': '__lshift__',
'%': '__mod__',
......@@ -515,6 +516,9 @@ class MethodDispatcherTransform(EnvTransform):
operand1, operand2 = node.operand1, node.operand2
if special_method_name == '__contains__':
operand1, operand2 = operand2, operand1
elif special_method_name == '__div__':
if Future.division in self.current_env().global_scope().context.future_directives:
special_method_name = '__truediv__'
obj_type = operand1.type
if obj_type.is_builtin_type:
type_name = obj_type.name
......
......@@ -495,13 +495,17 @@ static PyObject* __Pyx_PyInt_{{op}}{{order}}(PyObject *op1, PyObject *op2, long
#if CYTHON_COMPILING_IN_CPYTHON
{{py: from Cython.Utility import pylong_join }}
{{py: pyval, ival = ('op2', 'b') if order == 'CObj' else ('op1', 'a') }}
{{py: c_op = {'Add': '+', 'Subtract': '-', 'Remainder': '%', 'Or': '|', 'Xor': '^', 'And': '&', 'Rshift': '>>'}[op] }}
{{py: slot_name = {'TrueDivide': 'true_divide', 'FloorDivide': 'floor_divide'}.get(op, op.lower()) }}
{{py:
c_op = {'Add': '+', 'Subtract': '-', 'Remainder': '%', 'TrueDivide': '/', 'FloorDivide': '/',
'Or': '|', 'Xor': '^', 'And': '&', 'Rshift': '>>'}[op]
}}
static PyObject* __Pyx_PyInt_{{op}}{{order}}(PyObject *op1, PyObject *op2, CYTHON_UNUSED long intval, int inplace) {
#if PY_MAJOR_VERSION < 3
if (likely(PyInt_CheckExact({{pyval}}))) {
const long {{'a' if order == 'CObj' else 'b'}} = intval;
{{if c_op in '+-%'}}
{{if c_op in '+-%' or op == 'FloorDivide'}}
long x;
{{endif}}
long {{ival}} = PyInt_AS_LONG({{pyval}});
......@@ -512,7 +516,7 @@ static PyObject* __Pyx_PyInt_{{op}}{{order}}(PyObject *op1, PyObject *op2, CYTHO
x = (long)((unsigned long)a {{c_op}} b);
if (likely((x^a) >= 0 || (x^{{ '~' if op == 'Subtract' else '' }}b) >= 0))
return PyInt_FromLong(x);
return PyLong_Type.tp_as_number->nb_{{op.lower()}}(op1, op2);
return PyLong_Type.tp_as_number->nb_{{slot_name}}(op1, op2);
{{elif c_op == '%'}}
// modulus with differing signs isn't safely portable, emulate CPython
if (unlikely(a < 0)) {
......@@ -522,6 +526,30 @@ static PyObject* __Pyx_PyInt_{{op}}{{order}}(PyObject *op1, PyObject *op2, CYTHO
x = a % b;
}
return PyInt_FromLong(x);
{{elif op == 'TrueDivide'}}
if (8 * sizeof(long) <= 53 || likely({{ival}} <= (1L << 53) && {{ival}} >= (-(1L << 53)))) {
return PyFloat_FromDouble((double)a / (double)b);
}
// let Python do the rounding
return PyInt_Type.tp_as_number->nb_{{slot_name}}(op1, op2);
{{elif op == 'FloorDivide'}}
if ((a^b) >= 0) {
{{if order == 'ObjC'}}
// INT_MIN / -1 is the only case that overflows
if (unlikely(b == -1 && ((unsigned long)a) == 0-(unsigned long)a))
return PyInt_Type.tp_as_number->nb_{{slot_name}}(op1, op2);
{{endif}}
x = a / b;
} else {
// use manual rounding when result is negative (signs differ)
long la = labs(a), lb = labs(b);
x = la / lb;
if (x * lb != la)
x = -x - 1;
else
x = -x;
}
return PyInt_FromLong(x);
{{else}}
// other operations are safe, no overflow
return PyInt_FromLong(a {{c_op}} b);
......@@ -545,22 +573,39 @@ static PyObject* __Pyx_PyInt_{{op}}{{order}}(PyObject *op1, PyObject *op2, CYTHO
{{for _size in (2, 3, 4)}}
{{for _case in (-_size, _size)}}
case {{_case}}: {{if c_op != '%' or _case > 0}}
if (8 * sizeof(long) - 1 > {{_size}} * PyLong_SHIFT) {
if (8 * sizeof(long) - 1 > {{_size}} * PyLong_SHIFT{{if op == 'TrueDivide'}} && {{_size-1}} * PyLong_SHIFT < 53{{endif}}) {
{{ival}} = {{'-' if _case < 0 else ''}}(long) {{pylong_join(_size, 'digits')}};
break;
}
{{endif}}
// in negative case, fall through to positive calculation for '%'
// in negative case, fall through to positive calculation for '%' and '//'
// if size doesn't fit into a long anymore, fall through to default
{{endfor}}
{{endfor}}
default: return PyLong_Type.tp_as_number->nb_{{op.lower()}}(op1, op2);
default: return PyLong_Type.tp_as_number->nb_{{slot_name}}(op1, op2);
}
{{if c_op == '%'}}
x = a % b;
if (unlikely(size < 0) && x) {
x = b - x;
}
{{elif op == 'TrueDivide'}}
if (8 * sizeof(long) <= 53 || (size >= -52 / PyLong_SHIFT && size <= 52 / PyLong_SHIFT) || likely({{ival}} <= (1L << 53) && {{ival}} >= (-(1L << 53)))) {
return PyFloat_FromDouble((double)a / (double)b);
}
return PyLong_Type.tp_as_number->nb_{{slot_name}}(op1, op2);
{{elif op == 'FloorDivide'}}
if ((a^b) >= 0) {
x = a / b;
} else {
// use manual rounding when result is negative (signs differ)
long {{'la = %s(a), lb = %s(b)' % (('', 'labs') if order == 'ObjC' else ('labs', ''))}};
x = la / lb;
if (x * lb != la)
x = -x - 1;
else
x = -x;
}
{{else}}
x = a {{c_op}} b;
{{endif}}
......@@ -568,13 +613,13 @@ static PyObject* __Pyx_PyInt_{{op}}{{order}}(PyObject *op1, PyObject *op2, CYTHO
}
#endif
{{if c_op in '+-'}}
{{if c_op in '+-' or op == 'TrueDivide'}}
if (PyFloat_CheckExact({{pyval}})) {
const long {{'a' if order == 'CObj' else 'b'}} = intval;
double result;
double {{ival}} = PyFloat_AS_DOUBLE({{pyval}});
// copied from floatobject.c in Py3.5:
PyFPE_START_PROTECT("{{op.lower()}}", return NULL)
PyFPE_START_PROTECT("{{op.lower() if not op.endswith('Divide') else 'divide'}}", return NULL)
result = ((double)a) {{c_op}} (double)b;
PyFPE_END_PROTECT(result)
return PyFloat_FromDouble(result);
......@@ -599,7 +644,7 @@ static PyObject* __Pyx_PyFloat_{{op}}{{order}}(PyObject *op1, PyObject *op2, dou
#if CYTHON_COMPILING_IN_CPYTHON
{{py: from Cython.Utility import pylong_join }}
{{py: pyval, fval = ('op2', 'b') if order == 'CObj' else ('op1', 'a') }}
{{py: c_op = '+' if op == 'Add' else '-' }}
{{py: c_op = {'Add': '+', 'Subtract': '-', 'TrueDivide': '/'}[op] }}
static PyObject* __Pyx_PyFloat_{{op}}{{order}}(PyObject *op1, PyObject *op2, double floatval, int inplace) {
const double {{'a' if order == 'CObj' else 'b'}} = floatval;
......@@ -654,7 +699,7 @@ static PyObject* __Pyx_PyFloat_{{op}}{{order}}(PyObject *op1, PyObject *op2, dou
}
// copied from floatobject.c in Py3.5:
PyFPE_START_PROTECT("{{op.lower()}}", return NULL)
PyFPE_START_PROTECT("{{op.lower() if not op.endswith('Divide') else 'divide'}}", return NULL)
result = a {{c_op}} b;
PyFPE_END_PROTECT(result)
return PyFloat_FromDouble(result);
......
......@@ -36,15 +36,49 @@ def constants():
"""
return 1/2, 1//2, 5/2.0, 5//2.0, 5/2, 5//2
def py_mix(a):
"""
>>> py_mix(1)
(0.5, 0, 0.5, 0.0, 0.5, 0)
>>> py_mix(1.0)
(0.5, 0.0, 0.5, 0.0, 0.5, 0.0)
>>> 2**53 / 2.0
4503599627370496.0
>>> py_mix(2**53)
(4503599627370496.0, 4503599627370496, 4503599627370496.0, 4503599627370496.0, 4503599627370496.0, 4503599627370496)
>>> py_mix(2**53 + 1)
(4503599627370496.0, 4503599627370496, 4503599627370496.0, 4503599627370496.0, 4503599627370496.0, 4503599627370496)
>>> py_mix(2**53 + 1.0)
(4503599627370496.0, 4503599627370496.0, 4503599627370496.0, 4503599627370496.0, 4503599627370496.0, 4503599627370496.0)
"""
return a/2, a//2, a/2.0, a//2.0, a/2, a//2
def py_mix_by_neg1(a):
"""
>>> py_mix_by_neg1(0)
(-0.0, 0, -0.0, -0.0, -0.0, 0)
>>> py_mix_by_neg1(-1)
(1.0, 1, 1.0, 1.0, 1.0, 1)
>>> py_mix_by_neg1(int(2**31-1))
(-2147483647.0, -2147483647, -2147483647.0, -2147483647.0, -2147483647.0, -2147483647)
>>> py_mix_by_neg1(int(-2**31-1))
(2147483649.0, 2147483649, 2147483649.0, 2147483649.0, 2147483649.0, 2147483649)
>>> results = py_mix_by_neg1(int(2**63-1))
>>> results[0] == results[2] == results[3] == results[4] == float(2**63-1) / -1.0 or results
True
>>> results[1] == results[5] == (2**63-1) // -1 or results
True
>>> results = py_mix_by_neg1(int(-2**63-1))
>>> results[0] == results[2] == results[3] == results[4] == float(-2**63-1) / -1.0 or results
True
>>> results[1] == results[5] == (-2**63-1) // -1 or results
True
"""
return a/-1, a//-1, a/-1.0, a//-1.0, a/-1, a//-1
def py_mix_rev(a):
"""
>>> py_mix_rev(4)
......
......@@ -36,15 +36,49 @@ def constants():
"""
return 1/2, 1//2, 5/2.0, 5//2.0, 5/2, 5//2
def py_mix(a):
"""
>>> py_mix(1)
(0, 0, 0.5, 0.0, 0, 0)
>>> py_mix(1.0)
(0.5, 0.0, 0.5, 0.0, 0.5, 0.0)
>>> 2**53 / 2.0
4503599627370496.0
>>> py_mix(2**53)
(4503599627370496, 4503599627370496, 4503599627370496.0, 4503599627370496.0, 4503599627370496, 4503599627370496)
>>> py_mix(2**53 + 1)
(4503599627370496, 4503599627370496, 4503599627370496.0, 4503599627370496.0, 4503599627370496, 4503599627370496)
>>> py_mix(2**53 + 1.0)
(4503599627370496.0, 4503599627370496.0, 4503599627370496.0, 4503599627370496.0, 4503599627370496.0, 4503599627370496.0)
"""
return a/2, a//2, a/2.0, a//2.0, a/2, a//2
def py_mix_by_neg1(a):
"""
>>> py_mix_by_neg1(0)
(0, 0, -0.0, -0.0, 0, 0)
>>> py_mix_by_neg1(-1)
(1, 1, 1.0, 1.0, 1, 1)
>>> py_mix_by_neg1(int(2**31-1))
(-2147483647, -2147483647, -2147483647.0, -2147483647.0, -2147483647, -2147483647)
>>> py_mix_by_neg1(int(-2**31-1))
(2147483649, 2147483649, 2147483649.0, 2147483649.0, 2147483649, 2147483649)
>>> results = py_mix_by_neg1(int(2**63-1))
>>> results[2] == results[3] == float(2**63-1) / -1.0 or results
True
>>> results[0] == results[1] == results[4] == results[5] == (2**63-1) // -1 or results
True
>>> results = py_mix_by_neg1(int(-2**63-1))
>>> results[2] == results[3] == float(-2**63-1) / -1.0 or results
True
>>> results[0] == results[1] == results[4] == results[5] == (-2**63-1) // -1 or results
True
"""
return a/-1, a//-1, a/-1.0, a//-1.0, a/-1, a//-1
def py_mix_rev(a):
"""
>>> py_mix_rev(4)
......
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