Following are important components of a typical scientific application:
While all of the above are important components, the speed of numerical computations determine the success of a scientific application. While Python is quite strong in the rest of the requirements, it is severely deficient in speed. There are many reasons for this:
It is possible to improve performance of a program by choosing an efficient algorithm. But improvement in performance that can be achieved by writing code in a faster programming languae than Python, such as, C/C++ can enhance performance even of the best algorithm. Therefore the best approach to improving performance is by writing code in C, in such a way that it can be called in Python. The typical workflow for this is as follows:
The Cython project takes a slightly different approach but essentially does the same. Cython is a programming language based on Python but with additional syntax allowing for optional static type declarations, essentially making it a strongly typed language. The typical workflow is as follows:
Except for the first step, the workflow is the same as before. But there is a definite advantage in writing code in Python itself. As a result, any Python program is also a Cython program and is a potential candidate for performance enhancement. As anadded advantage, Cython makes it easy to integrate Python with external C libraries.
To use Cython, the following are required:
Following are the typical steps to use distutils:
Here is the Cython file hello.pyx containing the famous funtion that greets the World:
def say_hello():
print "Hello world!"
The corresponding setup.py file that uses distutils
is given below:
from distutils.core import setup
from Cython.Build import cythonize
setup(
ext_modules = cythonize("helloworld.pyx")
)
The setup.py
file imports the required setup
function and Extension
class from the submodules distutils.core
and distutils.extension
, respectively. It imports build_ext
class from Cython.Distutils
submodule.
The name of the Cython source file hello.pyx
and the name to be assigned to the Python importable module, namely, hello
are also specified.
The hello module is built using the following command:
$ python setup.py build_ext --inplace
This produces the hello.so file which can be imported into Python and used with as follows:
import hello
hello.say_hello()
This produces the output:
Hello, World!
Run the Cython compiler to convert the .pyx
file to generate a .c
file
$ cython -a hello.pyx
The -a
switch, which is optional, generates the hello.html
HTML file which can be opened in a browser. This contains the code with the lines highlighted in different shades of yellow. Lines in bright yellow highlighting require optimisation while lines with white background translate into direct C code. This generates the C file hello.c
.
The generated C code hello.c
must be compiled to an importable Python module hello.so
(hello.pyd
in Windows) using the C compiler:
gcc -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing \
-I/usr/include/python2.7 -o hello.so hello.c
It is important to tell where the Python.h
header file is located using the -I
switch. The generated shared object file hello.so
can then be imported into a Python program using the import hello
statement and used just as you would, any other module.
import hello
hello.say_hello('World')
Running this Python program generates the following output on the console:
Hello, World!
Using pyximport
module makes it unnecessary to write a setup.py
file. The typical import
statements while using pyximport
are as follows:
import pyximport
pyximport.install()
import hello
print hello.say_hello('World')
The import hello
statement actually carries out the task of compiling the .pyx
file to a .c
file. This C file is then compiled to a .so
(or a .pyd
) file by the import hello
statement. In fact, everythin happens transparently and you will not see the .c
and .so
files at all. If the first two lines where you import pyximport and the next line where we call the install()
function, you would not realise that several things are happening behind the scenes and you would think you were running an ordinary Python script.
In [1]:
import pyximport
pyximport.install()
import hello
hello.say_hello()
Let us demonstrate how Cython can improve performance of Python code by starting with a simple pure Python function and bring in speed improvements in phases.
Let us first write a pure Python script file quad.py
, containing two functions. The first function def f(a, b, N):
The IPython shell provides a magic function %timeit
to measure the time of execution of selected functions. It executes the specified function a certain number of times and reports the best time. This feature of IPython is used as shown on the last line above.
In [2]:
# file: quad.py - Pure Python code
from math import sin
def f(x):
return sin(x**2)
def integrate(a, b, N):
s = 0
dx = float(b - a) / N
for i in range(N+1):
s += f(a + i*dx)
return s * dx
%timeit integrate(0, 1, 100000)
In [3]:
import pyximport
pyximport.install()
import quad0
%timeit quad0.integrate(0, 1, 100000)
The execution time reduced from 50.6 ms to 26.8 ms, a reduction of 47% compared to the pure Python code.
Performance can be further improved if the data types of the variables can be specified. Here is the quad0.pyx
modified and named as quad1.pyx
# file: quad1.pyx - With type declaration
from math import sin
def f(double x):
return sin(x*x)
def integrate(double a, double b, int N):
cdef int i
cdef double s, dx
s = 0.0
dx = float(b - a) / N
for i in range(N):
s += f(a + i*dx)
return s * dx
The only changes to the pure Python code that we have made are the static data type declarations for the function parameters and local variables inside the function integrate()
. To compile and execute this script, we will again use pyximport
In [4]:
import pyximport
pyximport.install()
import quad1
%timeit quad1.integrate(0, 1, 100000)
The execution time is now reduced to 10.2 ms, a reduction of 79% compared to the pure Python script.
Cython can help us in identifying which part of the script needs improvement. To do this, run the cython
compiler with the -a
switch on the command line as follows:
cython -a quad1.pyx
This generates an HTML file quad1.html
which you can open in a browser. Lines marked with yellow background are potential candidates where speed improvements can be achieved. Thus, the return type of the function f()
can help speed up the program.
In [5]:
from IPython.display import HTML
HTML(filename='quad1.html')
Out[5]:
# file: quad2.pyx - Type declaration and function return value
from math import sin
cpdef double f(double x):
return sin(x*x)
def integrate(double a, double b, int N):
cdef int i
cdef double s, dx
s = 0.0
dx = float(b - a) / N
for i in range(N):
s += f(a + i*dx)
return s * dx
Let us now execute this script and see what improvement it can make to the code:
In [6]:
import pyximport
pyximport.install()
import quad2
%timeit quad1.integrate(0, 1, 100000)
The execution time is now down to 9.86 ms, a reduction of 80.5% compared to the pure Python script.
Another possibility is to call the sin
function from the C math
library directly. Here is the modified quad3.pyx
file with the changes made to the import
statement on the first line:
# file: quad3.pyx - Import function from C standard library
from libc.math cimport sin
cpdef double f(double x):
return sin(x*x)
def integrate(double a, double b, int N):
cdef int i
cdef double s, dx
s = 0.0
dx = float(b - a) / N
for i in range(N):
s += f(a + i*dx)
return s * dx
Let us test this modified script and see what happens:
In [7]:
import pyximport
pyximport.install()
import quad3
%timeit quad1.integrate(0, 1, 100000)
The execution time does not show any significant improvement. It can be seen that speed can be increased dramatically by using Cython by statically declaring the types of function parameters, function local variables, function return values and calling functions directly from the C library.
IPython provices a cythonmagic extension that contains a number of magic functions to simplify working with Cython. This extension is loaded with the %load_ext
magic as follows:
%load_ext cythonmagic
Once this extension is loaded, several Cython related magic become available.
In [8]:
%load_ext cythonmagic
In [9]:
%%cython_pyximport foo
def f(x):
return 4.0 * x
In [10]:
print f(10)
cython -a
switch to identify potential candidates for code optimizationA Python program can be profiled with the following command from the command line:
python -m cProfile -s time pythonfile.py
For this to work, cProfile
Python module must be installed.
In [ ]: