If we have a code on pure Python usually we can speed it up with some tiny changes using Cython.

In its core Cython is an intermediate between Python and C/C++. It allows you to write your Python code with minor changes and then translate it into C code.

As usual im referencing to official Cython documentation with all details.

The first thing we will do we will write our functions.

Lets use number calculations from other atricle in this blog.

We will start with recursive calculation.

# stern_python.py

def stern_n(n):
    if n < 2:
        return n
    elif n % 2 == 0:
        return stern_n(n/2)
    else:
        return stern_n((n - 1)/2) + stern_n((n + 1)/2)

def run_stern(n):
    stern_array = list()
    for i in range(n):
        stern_array.append(int(stern_n(i)))
    return stern_array
In [10]:
import stern_python as python_s

Our Cython equivalent of the same functions look very similar. For that we will create new file.

# stern_cython.pyx

cpdef int stern_n(int n):
    if n < 2:
        return n
    elif n % 2 == 0:
        return stern_n(n/2)
    else:
        return stern_n((n - 1)/2) + stern_n((n + 1)/2)

cpdef run_stern(int n):
    stern_array = list()
    for i in range(n):
        stern_array.append(stern_n(i))
    return stern_array

Next we will take list manupulation function with lazy calculation.

# hofsconwc_python.py

def hofsconwc(i):
    hf = []
    for n in range(i):
        if n <= 2:
            hf.append(1)
        else:
            hf.append(hf[hf[n-1]] + hf[n - hf[n-2]-1])
    return hf
In [19]:
import hofsconwc_python as python_h

and our Cython variant.

# hofsconwc_python.pyx

cpdef hofsconwc(int i):
    hf = []
    for n in range(i):
        if n <= 2:
            hf.append(1)
        else:
            hf.append(hf[hf[n-1]] + hf[n - hf[n-2]-1])
    return hf

Next we will create setup.py file which will compile the Cython into C code.

We will use *.pyx as filename because we wanna try multiple files compilation.

# setup.py

from distutils.core import setup
from Cython.Build import cythonize

setup(name='Testing calculation',
      ext_modules=cythonize('*.pyx'),
      requires=['Cython'], )
In [15]:
!ls -ll
total 224
-rw-rw-r-- 1 pavel pavel    187 ноя  7 13:21  hofsconwc_cython.pyx
-rw-rw-r-- 1 pavel pavel    180 ноя  7 13:20  hofsconwc_python.py
drwxrwxr-x 2 pavel pavel   4096 ноя  7 13:31  __pycache__
-rw-r--r-- 1 pavel pavel     39 окт 22 18:46  README.md
-rw-rw-r-- 1 pavel pavel   1006 ноя  7 12:20  run_test.py
-rw-rw-r-- 1 pavel pavel    170 ноя  7 12:14  setup.py
-rw-rw-r-- 1 pavel pavel    305 окт 22 20:41  stern_cython.pyx
-rw-rw-r-- 1 pavel pavel    295 окт 22 20:01  stern_python.py
-rw-r--r-- 1 pavel pavel 189442 ноя  7 13:48 'Use Cython to speedup your Python code.ipynb'
drwxrwxr-x 7 pavel pavel   4096 ноя  7 12:32  venv

All we need to do is just perform

In [17]:
!python setup.py build_ext --inplace
running build_ext
In [18]:
!ls -ll
total 680
drwxrwxr-x 3 pavel pavel   4096 ноя  7 13:48  build
-rw-rw-r-- 1 pavel pavel 132506 ноя  7 13:48  hofsconwc_cython.c
-rwxrwxr-x 1 pavel pavel 119808 ноя  7 13:48  hofsconwc_cython.cpython-37m-x86_64-linux-gnu.so
-rw-rw-r-- 1 pavel pavel    187 ноя  7 13:21  hofsconwc_cython.pyx
-rw-rw-r-- 1 pavel pavel    180 ноя  7 13:20  hofsconwc_python.py
drwxrwxr-x 2 pavel pavel   4096 ноя  7 13:31  __pycache__
-rw-r--r-- 1 pavel pavel     39 окт 22 18:46  README.md
-rw-rw-r-- 1 pavel pavel   1006 ноя  7 12:20  run_test.py
-rw-rw-r-- 1 pavel pavel    170 ноя  7 12:14  setup.py
-rw-rw-r-- 1 pavel pavel 109538 ноя  7 13:48  stern_cython.c
-rwxrwxr-x 1 pavel pavel  93632 ноя  7 13:48  stern_cython.cpython-37m-x86_64-linux-gnu.so
-rw-rw-r-- 1 pavel pavel    305 окт 22 20:41  stern_cython.pyx
-rw-rw-r-- 1 pavel pavel    295 окт 22 20:01  stern_python.py
-rw-r--r-- 1 pavel pavel 189442 ноя  7 13:48 'Use Cython to speedup your Python code.ipynb'
drwxrwxr-x 7 pavel pavel   4096 ноя  7 12:32  venv

It works! Now we can import Cython functions.

In [20]:
import stern_cython as cython_s
import hofsconwc_cython as cython_h

So we are ready to test our calculations with some magic functions.

In [28]:
%timeit _ = python_s.run_stern(10000)
757 ms ± 39.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [29]:
%timeit _ = cython_s.run_stern(10000)
7.26 ms ± 80.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Yes we have x100 time speed up with just type definition and precompiling our functions.

But what if we will try more real functions like hofsconwc without useless recursion.

In [31]:
%timeit _ = python_h.hofsconwc(10000)
2.75 ms ± 375 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [32]:
%timeit _ = cython_h.hofsconwc(10000)
1.4 ms ± 26.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Yes so easy we get x2 speed up for our calculations.

In [ ]:
 

Comments

comments powered by Disqus