IPython is an interactive shell for Python offering enhanced introspection, tab completion and rich history. With little modifications and some special syntaxes, we can run it for Cython also. It has some classic tools which make things like memory management, optimization etc handy.

Installation:-

1. For linux:- Type in terminal:

>>> sudo easy_install ipython

2. Then we also need to install readline using command:

>>> sudo easy_install readline

Now we are ready to go.

To use cython in Ipython we need to load cython magic using :

In [1]: %load_ext cythonmagic

This enables all cython magic functions in IPython.

Now lets say we want to write a cython function of adding two integers. We can do it in shell itself. What we should have done without it:- Write the function in a .pyx file. Compile it using setup.py ( either using distutils or manually ). Then load import it in shell. But using IPython it’s too easy. Just add %%cython magic command as the first line and then write cython code. It does all the remaining booriing work for you.
Cython function to add two integers:-

In [4]: %%cython
   ...: def sum(int a, int b):
   ...:     cdef int s = a+b
   ...:     return s
   ...: 

This is all that you need to do. Al the compiling and importing is done by IPython itself. Remember the %%cython command is valid for only that block. If you want to define another cython function start again with %%cython magic function.

Now lets test it.

In [6]: sum(1205, 2565)
Out[6]: 3770

Yes its working…!!! But what about raising errors?
Lets try..

In [6]: sum(152, 'dhn')
Out[6]: 3770

TypeError                                 
Traceback (most recent call last)
 in ()
----> 1 sum(152, 'dhn')
/home/aman/.cache/ipython/cython/_cython_magic_161302c79052187807a09738c3cc5bd1.so in _cython_
magic_161302c79052187807a09738c3cc5bd1.sum (/home/aman/.cache/ipython/cython/_cython_magic_161
302c79052187807a09738c3cc5bd1.c:615)()

TypeError: an integer is required

It raises error for wrong data types also.

Now lets come to bench-marking and optimization.  IPython provide many which automatically measure the execution time of a function and give detailed report.

The first one and easiest to use is %timeit. It gives best execution time after running the function many times.

Lets use it for above defined function:

In [11]: %timeit sum
10000000 loops, best of 3: 20.4 ns per loop

We can manually modify no of times it runs. But for that we need to import some functions as:-

In [13]: from timeit import Timer, timeit, repeat

Then type:

Repeat = repeat("sum(255, 5895)", setup="from __main__ import sum", repeat=3, number=100000)

repeat argument defines how many times you want to do the check.

number arguments defines number of times you want to run the function to get best from it.

Output with repeat = 3 comes out to be:

[0.015902996063232422, 0.006285905838012695, 0.006062984466552734]

There is another very handy tool %prun. It tells the detailed report of time taken by various functions while execution.

Lets define another function which will help in understanding %prun.

In [6]: %%cython
...: def sq(int a):
...: return a*a
...:
In [7]: %%cython
....: from __main__ import sq
....: def fun(int a, int b):
....: return sq(a)+sq(b)
....:

NB: To import sq the command is  from __main__ import sq

Now lets create arguments for the functions:

In [17]: a = no.linspace(10,1500,150000)

In [18]: b = no.linspace(100,1500,150000)

We nee to run this function many times since its very small and the time taken will be very small for bench-marking:

In [24]: %prun [fun(250, 254) for _ in range(1000000)]

Output we got is:-

1000003 function calls in 0.541 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.301    0.301    0.541    0.541 :1()
  1000000    0.223    0.000    0.223    0.000 {_cython_magic_83e0908c3b53c6e543c5774b7.fun}
        1    0.017    0.017    0.017    0.017 {range}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' object

Meaning of different arguments is:

ncalls : for the number of calls,
tottime: for the total time spent in the given function (and excluding time made in calls to sub-functions),
percall: is the quotient of tottime divided by ncalls
cumtime: is the total time spent in this and all subfunctions (from invocation till exit). This figure is accurate even for recursive functions.
percall: is the quotient of cumtime divided by primitive calls

There are other tools like %lprun an some other for memory profiling. We can use them according to need.

Ipython has another very important and useful feature. Using it we can directly import any cython (.pyx file) as we do with .py (python) files. The file can be reloaded also. The corrsponding commands are:-

import pyximport
pyximport.install(reload_support=True)  #for relaoding
import my_cython_module #file name is my_cython_module.pyx

That’s all for now.

</keep coding>

Advertisements