Reputation: 575
I am trying to modify an old Python code into Cython to improve speed. A class has a init function allowing various types of parameter:
class dRect:
def __init__(self, *args):
if len(args) == 1: # actual input is a list of four float numbers
self.x0, self.y0, self.x1, self.y1 = args[0]
elif len(args) == 4: # actual input is four float numbers
self.x0, self.y0, self.x1, self.y1 = args
# sample of initiating dRect instance
a = dRect([0, 1, 2, 3])
b = dRect(0, 1, 2, 3)
What is the proper way to modify it into a Cython code? Or which part of the documentation should I dive into? Thanks.
Upvotes: 1
Views: 176
Reputation: 313
The amount of speed up (in any) depends on what you are allow to do (up to 10x boost in my case).
Test:
cdef class MyCyClass:
cdef public double x0
cdef public double x1
cdef public double x2
cdef public double x3
def __init__(self, *args):
self.x0 = args[0]
self.x1 = args[1]
self.x2 = args[2]
self.x3 = args[3]
class MyPyClass:
def __init__(self, *args):
self.x0 = args[0]
self.x1 = args[1]
self.x2 = args[2]
self.x3 = args[3]
cdef new_MyCyClass_from_seq(args):
my_obj = MyCyClass.__new__(MyCyClass)
init_MyCyClass_from_seq(my_obj, args)
cdef init_MyCyClass_from_seq(MyCyClass my_obj, args):
my_obj.x0 = args[0]
my_obj.x1 = args[1]
my_obj.x2 = args[2]
my_obj.x3 = args[3]
cdef new_MyCyClass_from_double(double x0, double x1, double x2, double x3):
my_obj = MyCyClass.__new__(MyCyClass)
init_MyCyClass_from_double(my_obj, x0, x1, x2, x3)
cdef init_MyCyClass_from_double(MyCyClass my_obj, double x0, double x1, double x2, double x3):
my_obj.x0 = x0
my_obj.x1 = x1
my_obj.x2 = x2
my_obj.x3 = x3
def timefunc(name):
def timedecorator(f):
cdef Py_ssize_t L, i
print("Running", name)
for L in [1, 10, 100, 1000, 10000]:
start = time.perf_counter()
f(L)
end = time.perf_counter()
print(format((end-start) / loops * 1e6, "2f"), end=" ")
sys.stdout.flush()
print("μs")
return timedecorator
print()
print("INITIALISATIONS")
cdef Py_ssize_t loops = 100000
@timefunc("use constructor of non-cdef class with type(input) = float")
def _(Py_ssize_t L):
cdef Py_ssize_t i
my_obj = MyPyClass(1., 2., 3., 4.)
for i in range(loops):
my_obj = MyPyClass(1., 2., 3., 4.)
return my_obj
@timefunc("use constructor (cdef) with type(input) = float")
def _(Py_ssize_t L):
cdef MyCyClass my_obj = MyCyClass(1., 2., 3., 4.)
cdef Py_ssize_t i
for i in range(loops):
# Notice the decimal place to denote
my_obj = MyCyClass(1., 2., 3., 4.)
return my_obj
@timefunc("use constructor (cdef) with type(input) = int")
def _(Py_ssize_t L):
cdef MyCyClass my_obj = MyCyClass(1, 2, 3, 4)
cdef Py_ssize_t i
for i in range(loops):
my_obj = MyCyClass(1, 2, 3, 4)
return my_obj
@timefunc("use new (cdef) with double init, input is double")
def _(Py_ssize_t L):
cdef MyCyClass my_obj = MyCyClass(1, 2, 3, 4)
cdef Py_ssize_t i
for i in range(loops):
my_obj = new_MyCyClass_from_double(1., 2., 3., 4.)
return my_obj
@timefunc("use new (cdef) with double init, input is int")
def _(Py_ssize_t L):
cdef MyCyClass my_obj = MyCyClass(1, 2, 3, 4)
cdef Py_ssize_t i
for i in range(loops):
new_MyCyClass_from_double(1, 2, 3, 4)
return my_obj
@timefunc("use new (cdef) and tuple init")
def _(Py_ssize_t L):
cdef MyCyClass my_obj = MyCyClass(1., 2., 3., 4.)
cdef Py_ssize_t i
for i in range(loops):
my_obj = new_MyCyClass_from_seq((1., 2., 3., 4.))
return my_obj
Here are my timings
Running use constructor of non-cdef class with type(input) = float
Running use constructor (cdef) with type(input) = float
Running use constructor (cdef) with type(input) = int
Running use new (cdef) with double init, input is double
Running use new (cdef) with double init, input is int
Running use new (cdef) and tuple init
Upvotes: 2