Commit 52099c3e authored by Stefan Behnel's avatar Stefan Behnel

speed up Python object calculation of 2**N

parent 39bf23cc
...@@ -9,6 +9,8 @@ Latest ...@@ -9,6 +9,8 @@ Latest
Features added Features added
-------------- --------------
* The Python expression "2 ** N" was optimised.
* Simple support for declaring Python object types in Python signature * Simple support for declaring Python object types in Python signature
annotations. Currently requires setting the compiler directive annotations. Currently requires setting the compiler directive
``annotation_typing=True``. ``annotation_typing=True``.
......
...@@ -8111,7 +8111,7 @@ class UnopNode(ExprNode): ...@@ -8111,7 +8111,7 @@ class UnopNode(ExprNode):
self.generate_py_operation_code(code) self.generate_py_operation_code(code)
def generate_py_operation_code(self, code): def generate_py_operation_code(self, code):
function = self.py_operation_function() function = self.py_operation_function(code)
code.putln( code.putln(
"%s = %s(%s); %s" % ( "%s = %s(%s); %s" % (
self.result(), self.result(),
...@@ -8187,7 +8187,7 @@ class UnaryPlusNode(UnopNode): ...@@ -8187,7 +8187,7 @@ class UnaryPlusNode(UnopNode):
self.type = PyrexTypes.widest_numeric_type( self.type = PyrexTypes.widest_numeric_type(
self.operand.type, PyrexTypes.c_int_type) self.operand.type, PyrexTypes.c_int_type)
def py_operation_function(self): def py_operation_function(self, code):
return "PyNumber_Positive" return "PyNumber_Positive"
def calculate_result_code(self): def calculate_result_code(self):
...@@ -8213,7 +8213,7 @@ class UnaryMinusNode(UnopNode): ...@@ -8213,7 +8213,7 @@ class UnaryMinusNode(UnopNode):
if self.type.is_complex: if self.type.is_complex:
self.infix = False self.infix = False
def py_operation_function(self): def py_operation_function(self, code):
return "PyNumber_Negative" return "PyNumber_Negative"
def calculate_result_code(self): def calculate_result_code(self):
...@@ -8239,7 +8239,7 @@ class TildeNode(UnopNode): ...@@ -8239,7 +8239,7 @@ class TildeNode(UnopNode):
else: else:
self.type_error() self.type_error()
def py_operation_function(self): def py_operation_function(self, code):
return "PyNumber_Invert" return "PyNumber_Invert"
def calculate_result_code(self): def calculate_result_code(self):
...@@ -8874,6 +8874,7 @@ def get_compile_time_binop(node): ...@@ -8874,6 +8874,7 @@ def get_compile_time_binop(node):
% node.operator) % node.operator)
return func return func
class BinopNode(ExprNode): class BinopNode(ExprNode):
# operator string # operator string
# operand1 ExprNode # operand1 ExprNode
...@@ -8990,7 +8991,7 @@ class BinopNode(ExprNode): ...@@ -8990,7 +8991,7 @@ class BinopNode(ExprNode):
def generate_result_code(self, code): def generate_result_code(self, code):
#print "BinopNode.generate_result_code:", self.operand1, self.operand2 ### #print "BinopNode.generate_result_code:", self.operand1, self.operand2 ###
if self.operand1.type.is_pyobject: if self.operand1.type.is_pyobject:
function = self.py_operation_function() function = self.py_operation_function(code)
if self.operator == '**': if self.operator == '**':
extra_args = ", Py_None" extra_args = ", Py_None"
else: else:
...@@ -9024,7 +9025,7 @@ class CBinopNode(BinopNode): ...@@ -9024,7 +9025,7 @@ class CBinopNode(BinopNode):
node.type = PyrexTypes.error_type node.type = PyrexTypes.error_type
return node return node
def py_operation_function(self): def py_operation_function(self, code):
return "" return ""
def calculate_result_code(self): def calculate_result_code(self):
...@@ -9162,7 +9163,7 @@ class NumBinopNode(BinopNode): ...@@ -9162,7 +9163,7 @@ class NumBinopNode(BinopNode):
type2.is_unicode_char or type2.is_unicode_char or
BinopNode.is_py_operation_types(self, type1, type2)) BinopNode.is_py_operation_types(self, type1, type2))
def py_operation_function(self): def py_operation_function(self, code):
function_name = self.py_functions[self.operator] function_name = self.py_functions[self.operator]
if self.inplace: if self.inplace:
function_name = function_name.replace('PyNumber_', 'PyNumber_InPlace') function_name = function_name.replace('PyNumber_', 'PyNumber_InPlace')
...@@ -9228,7 +9229,7 @@ class AddNode(NumBinopNode): ...@@ -9228,7 +9229,7 @@ class AddNode(NumBinopNode):
return NumBinopNode.compute_c_result_type( return NumBinopNode.compute_c_result_type(
self, type1, type2) self, type1, type2)
def py_operation_function(self): def py_operation_function(self, code):
type1, type2 = self.operand1.type, self.operand2.type type1, type2 = self.operand1.type, self.operand2.type
if type1 is unicode_type or type2 is unicode_type: if type1 is unicode_type or type2 is unicode_type:
if type1.is_builtin_type and type2.is_builtin_type: if type1.is_builtin_type and type2.is_builtin_type:
...@@ -9236,7 +9237,7 @@ class AddNode(NumBinopNode): ...@@ -9236,7 +9237,7 @@ class AddNode(NumBinopNode):
return '__Pyx_PyUnicode_ConcatSafe' return '__Pyx_PyUnicode_ConcatSafe'
else: else:
return '__Pyx_PyUnicode_Concat' return '__Pyx_PyUnicode_Concat'
return super(AddNode, self).py_operation_function() return super(AddNode, self).py_operation_function(code)
class SubNode(NumBinopNode): class SubNode(NumBinopNode):
...@@ -9499,7 +9500,7 @@ class ModNode(DivNode): ...@@ -9499,7 +9500,7 @@ class ModNode(DivNode):
self.operand1.result(), self.operand1.result(),
self.operand2.result()) self.operand2.result())
def py_operation_function(self): def py_operation_function(self, code):
if self.operand1.type is unicode_type: if self.operand1.type is unicode_type:
if self.operand1.may_be_none(): if self.operand1.may_be_none():
return '__Pyx_PyUnicode_FormatSafe' return '__Pyx_PyUnicode_FormatSafe'
...@@ -9510,7 +9511,7 @@ class ModNode(DivNode): ...@@ -9510,7 +9511,7 @@ class ModNode(DivNode):
return '__Pyx_PyString_FormatSafe' return '__Pyx_PyString_FormatSafe'
else: else:
return '__Pyx_PyString_Format' return '__Pyx_PyString_Format'
return super(ModNode, self).py_operation_function() return super(ModNode, self).py_operation_function(code)
class PowNode(NumBinopNode): class PowNode(NumBinopNode):
...@@ -9551,6 +9552,17 @@ class PowNode(NumBinopNode): ...@@ -9551,6 +9552,17 @@ class PowNode(NumBinopNode):
typecast(self.operand1), typecast(self.operand1),
typecast(self.operand2)) typecast(self.operand2))
def py_operation_function(self, code):
if (self.type.is_pyobject and
self.operand1.constant_result == 2 and
self.operand2.type is py_object_type):
code.globalstate.use_utility_code(UtilityCode.load_cached('PyNumberPow2', 'Optimize.c'))
if self.inplace:
return '__Pyx_PyNumber_InPlacePowerOf2'
else:
return '__Pyx_PyNumber_PowerOf2'
return super(PowNode, self).py_operation_function(code)
# Note: This class is temporarily "shut down" into an ineffective temp # Note: This class is temporarily "shut down" into an ineffective temp
# allocation mode. # allocation mode.
......
...@@ -421,3 +421,44 @@ static double __Pyx__PyObject_AsDouble(PyObject* obj) { ...@@ -421,3 +421,44 @@ static double __Pyx__PyObject_AsDouble(PyObject* obj) {
bad: bad:
return (double)-1; return (double)-1;
} }
/////////////// PyNumberPow2.proto ///////////////
#define __Pyx_PyNumber_InPlacePowerOf2(a, b, c) __Pyx__PyNumber_PowerOf2(a, b, c, 1)
#define __Pyx_PyNumber_PowerOf2(a, b, c) __Pyx__PyNumber_PowerOf2(a, b, c, 0)
static PyObject* __Pyx__PyNumber_PowerOf2(PyObject *two, PyObject *exp, PyObject *none, int inplace); /*proto*/
/////////////// PyNumberPow2 ///////////////
static PyObject* __Pyx__PyNumber_PowerOf2(PyObject *two, PyObject *exp, PyObject *none, int inplace) {
// in CPython, 1<<N is substantially faster than 2**N
// TODO: disable this in Py3.5 if http://bugs.python.org/issue21420 gets accepted
#if CYTHON_COMPILING_IN_CPYTHON
Py_ssize_t shiftby;
if (likely(PyLong_Check(exp))) {
shiftby = PyLong_AsSsize_t(exp);
#if PY_MAJOR_VERSION < 3
} else if (likely(PyInt_Check(exp))) {
shiftby = PyInt_AsLong(exp);
#endif
} else {
goto fallback;
}
if (likely(shiftby >= 0)) {
if ((size_t)shiftby <= sizeof(long) * 8 - 2) {
long value = 1L << shiftby;
return PyInt_FromLong(value);
} else {
PyObject *one = PyInt_FromLong(1L);
if (unlikely(!one)) return NULL;
return PyNumber_Lshift(one, exp);
}
} else if (shiftby == -1 && PyErr_Occurred()) {
PyErr_Clear();
}
fallback:
#endif
return (inplace ? PyNumber_InPlacePower : PyNumber_Power)(two, exp, none);
}
...@@ -10,6 +10,7 @@ def f(obj2, obj3): ...@@ -10,6 +10,7 @@ def f(obj2, obj3):
obj1 = obj2 ** obj3 obj1 = obj2 ** obj3
return flt1, obj1 return flt1, obj1
def g(i): def g(i):
""" """
>>> g(4) >>> g(4)
...@@ -17,6 +18,7 @@ def g(i): ...@@ -17,6 +18,7 @@ def g(i):
""" """
return i ** 5 return i ** 5
def h(i): def h(i):
""" """
>>> h(4) >>> h(4)
...@@ -24,6 +26,7 @@ def h(i): ...@@ -24,6 +26,7 @@ def h(i):
""" """
return 5 ** i return 5 ** i
def constant_py(): def constant_py():
""" """
>>> constant_py() == 2 ** 10 >>> constant_py() == 2 ** 10
...@@ -32,6 +35,7 @@ def constant_py(): ...@@ -32,6 +35,7 @@ def constant_py():
result = (<object>2) ** 10 result = (<object>2) ** 10
return result return result
def constant_long(): def constant_long():
""" """
>>> constant_long() == 2 ** 36 >>> constant_long() == 2 ** 36
...@@ -40,6 +44,7 @@ def constant_long(): ...@@ -40,6 +44,7 @@ def constant_long():
result = (<object>2L) ** 36 result = (<object>2L) ** 36
return result return result
def small_int_pow(long s): def small_int_pow(long s):
""" """
>>> small_int_pow(3) >>> small_int_pow(3)
...@@ -49,6 +54,7 @@ def small_int_pow(long s): ...@@ -49,6 +54,7 @@ def small_int_pow(long s):
""" """
return s**0, s**1, s**2, s**3, s**4 return s**0, s**1, s**2, s**3, s**4
def int_pow(short a, short b): def int_pow(short a, short b):
""" """
>>> int_pow(7, 2) >>> int_pow(7, 2)
...@@ -59,3 +65,30 @@ def int_pow(short a, short b): ...@@ -59,3 +65,30 @@ def int_pow(short a, short b):
1024 1024
""" """
return a**b return a**b
def optimised_pow2(n):
"""
>>> optimised_pow2(0)
1
>>> optimised_pow2(1)
2
>>> optimised_pow2(10)
1024
>>> optimised_pow2(30)
1073741824
>>> print(repr(optimised_pow2(32)).rstrip('L'))
4294967296
>>> print(repr(optimised_pow2(100)).rstrip('L'))
1267650600228229401496703205376
>>> optimised_pow2(30000) == 2 ** 30000
True
>>> optimised_pow2(-1)
0.5
>>> optimised_pow2(0.5) == 2 ** 0.5
True
>>> optimised_pow2('test')
Traceback (most recent call last):
TypeError: unsupported operand type(s) for ** or pow(): 'int' and 'str'
"""
return 2 ** n
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