The use of numpy arrays, or CPython lists for doing element-wise operations (addition, subtraction, multiplication, division) is quite slow if the arrays you are operating on are relatively small (~10 elements). The overhead of array creation further penalizes the numpy solution in particular. Using a subclassed CPython list in Cython you can achieve better speed than either the numpy solution or the CPython list. In the attachment zip file listmath.zip are included a pure-Python subclassed list implementation of the four fundamental operations, a Cython implementation of the same, and a distutils setup file. The Cython extension can be created by running
python setup.py build_ext --inplace
in the folder that the files are in.
Then the file listmath.py can be run which times an element-wise operation should provide the output something like
Using listm [the pure-Python subclassed list], time is 3.35039 us/call
Using numpy, time is 1.97892 us/call
Using Cython extension type, time is 0.947084 us/callSo the Cython version is 2 times faster than even the numpy solution, and has simple syntax that parallels that of the numpy code.
Essentially the Cython solution overloads the +,*,/,- operators and uses the Cython special methods. So you could create a listm instance from a "normal" list by doing
from _listmath import listm a=[1,2,3] am=listm(a)
like is done in the benchmarking code in listmath.py. Finally here is the code for the math-enabled list:
_listmath.pyx
import cython
cimport cython
cdef class listm(list):
"""
See http://docs.cython.org/src/userguide/special_methods.html
"""
def __add__(self,y):
cdef int i,N
if isinstance(self,listm):
N=len(self)
if isinstance(y,int) or isinstance(y,float):
return listm([self[i]+y for i in range(N)])
else:
return listm([self[i]+y[i] for i in range(N)])
else:
### it is backwards, self is something else, y is a listm
N=len(y)
if isinstance(self,int) or isinstance(self,float):
return listm([y[i]+self for i in range(N)])
else:
return listm([self[i]+y[i] for i in range(N)])
def __mul__(self,y):
cdef int i,N
if isinstance(self,listm):
N=len(self)
if isinstance(y,int) or isinstance(y,float):
return listm([self[i]*y for i in range(N)])
else:
return listm([self[i]*y[i] for i in range(N)])
else:
### it is backwards, self is something else, y is a listm
N=len(y)
if isinstance(self,int) or isinstance(self,float):
return listm([y[i]*self for i in range(N)])
else:
return listm([self[i]*y[i] for i in range(N)])
def __truediv__(self,y):
cdef int i,N
if isinstance(self,listm):
N=len(self)
if isinstance(y,int) or isinstance(y,float):
return listm([self[i]/y for i in range(N)])
else:
return listm([self[i]/y[i] for i in range(N)])
else:
### it is backwards, self is something else, y is a listm
N=len(y)
if isinstance(self,int) or isinstance(self,float):
return listm([self/y[i] for i in range(N)])
else:
return listm([self[i]/y[i] for i in range(N)])
def __sub__(self,y):
cdef int i,N
if isinstance(self,listm):
N=len(self)
if isinstance(y,int) or isinstance(y,float):
return listm([self[i]-y for i in range(N)])
else:
return listm([self[i]-y[i] for i in range(N)])
else:
### it is backwards, self is something else, y is a listm
N=len(y)
if isinstance(self,int) or isinstance(self,float):
return listm([self-y[i] for i in range(N)])
else:
return listm([self[i]-y[i] for i in range(N)])