Showing posts with label Cython. Show all posts
Showing posts with label Cython. Show all posts

Thursday, December 23, 2010

Cython 3: my own .c source file

Yet more on elementary Cython. I have a working example with my own C code, thanks to help from the Cython users' list (here)---see the UPDATE below for one small change:

hello2.c:

#include <stdio.h>
void f();

void f() {
printf("%s", "Hello world!\n");
}

hello2_caller.pyx:

cdef extern from "hello2.c":
void f()

cpdef myf():
f()

setup.hello2_caller.py:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

sourcefiles = ['hello2_caller.pyx']
ext_modules = [Extension("hello2_caller",
sourcefiles
)]

setup(
name = 'Hello2 app',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules
)

build it:

python setup.hello2_caller.py build_ext --inplace

import and use it:

>>> import hello2_caller
>>> hello2_caller.myf()
Hello world!

The compiler complains:

In file included from hello2_caller.c:219:
hello2.c:3: warning: function declaration isn’t a prototype
hello2.c:5: warning: function declaration isn’t a prototype

But it works.

UPDATE: the problem is that these two declarations are not the same:

void f();
void f(void);

and we need to have the second one.


$ python setup.hello2_caller.py build_ext --inplace
running build_ext
cythoning hello2_caller.pyx to hello2_caller.c
building 'hello2_caller' extension
creating build
creating build/temp.macosx-10.6-universal-2.6
gcc-4.2 -fno-strict-aliasing -fno-common -dynamic -DNDEBUG -g -fwrapv -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE -arch i386 -arch ppc -arch x86_64 -pipe -I/System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6 -c hello2_caller.c -o build/temp.macosx-10.6-universal-2.6/hello2_caller.o
gcc-4.2 -Wl,-F. -bundle -undefined dynamic_lookup -arch i386 -arch ppc -arch x86_64 build/temp.macosx-10.6-universal-2.6/hello2_caller.o -o hello2_caller.so



$ python
Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49)
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello2_caller
>>> hello2_caller.myf()
Hello world!


Cython 2

I've been puzzling over the Cython docs and tutorial for quite a few hours now, trying to write a simple C function (in a .c file) and compile and call it from Cython. Like the example from before (here), except using my own function rather than a C math library call. I can't figure it out, so I posted questions on Stack Overflow and the Cython users list. While I'm taking a break I decided to play a bit with Fibonacci numbers.

The setup is simple: in cy.pyx (see below) we have two alternative implementations that compute the Nth element of the Fibonacci series. One version has Cython declarations that the variables are ints (and one for the function itself). The second version is plain Python. In a simple Python script cy.py we do

import pyximport
pyximport.install()
import cy

def f(N): cy.cyfib(N)
def g(N): cy.pyfib(N)

This translates the Python to C, builds it, and makes it accessible from within Python. We have to "wrap" the functions in Python calls in order to use timeit (at least, that's how I did it). Then we go to the command line and compare:

$ python -m timeit -s 'import cypy' 'cypy.f(1000)'
1000000 loops, best of 3: 1.37 usec per loop
$ python -m timeit -s 'import cypy' 'cypy.g(1000)'
1000 loops, best of 3: 232 usec per loop

The Cython version is quite a bit faster! Curiously cdef'ing the temp variable slows the timing down by a factor of 10. Hmm..

[UPDATE: There are issues!


>>> cy.cyfib(10)
55
>>> cy.cyfib(100)
-980107325
>>> cy.pyfib(100)
354224848179261915075L

I edit to use double instead of int:

>>> cy.cyfib(100)
3.54224848179262e+20
>>> cy.pyfib(100)
354224848179261915075L

It's rounded, but at least it's not negative!


]

import numpy

cpdef cyfib(int N):
cdef int a
cdef int b
cdef int counter
a = 1
b = 1
counter = 2
#cdef temp
while counter < N:
temp = a + b
a = b
b = temp
counter += 1
return b

def pyfib(N):
a,b = 1,1
counter = 2
while counter < N:
temp = a + b
a = b
b = temp
counter += 1
return b

Saturday, December 18, 2010

Exploring Cython, baby steps

I'm looking at Cython. From the docs:
Cython’s two major use cases [are]: extending the CPython interpreter with fast binary modules, and interfacing Python code with external C libraries.

The idea is that

The source code gets translated into optimized C/C++ code and compiled as Python extension modules

The "Hello world" example works fine. I install Cython with easy_install. Following the tutorial, in hello.pyx I have:

def say_hello_to(name):
print("Hello %s!" % name)

I write another file setup.hello.py:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [Extension("hello", ["hello.pyx"])]

setup(
name = 'Hello world app',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules
)

And I do this:

$ python setup.hello.py build_ext --inplace
running build_ext
cythoning hello.pyx to hello.c
building 'hello' extension
gcc-4.2 -fno-strict-aliasing -fno-common -dynamic -DNDEBUG -g -fwrapv -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE -arch i386 -arch ppc -arch x86_64 -pipe -I/System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6 -c hello.c -o build/temp.macosx-10.6-universal-2.6/hello.o
gcc-4.2 -Wl,-F. -bundle -undefined dynamic_lookup -arch i386 -arch ppc -arch x86_64 build/temp.macosx-10.6-universal-2.6/hello.o -o hello.so

From the interpreter we can import functions from hello.so:

>>> from hello import say_hello_to
>>> say_hello_to('Tom')
Hello Tom!

Now, to do something a bit more interesting. Suppose I want to use the sqrt function from the C library declared in math.h. I write a file cy_script.pyx:

cdef extern from "math.h":
double sqrt(double)

cdef double f(int i):
return sqrt(i)

print f(2)

The print statement is there for testing, as you'll see. We write another file setup.py with this:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules=[
Extension("cy_script",
["cy_script.pyx"],
libraries=["m"]) # Unix-like specific
]

setup(
name = "Cy_script",
cmdclass = {"build_ext": build_ext},
ext_modules = ext_modules
)

We build it as before:

$ python setup.py build_ext --inplace
running build_ext
cythoning cy_script.pyx to cy_script.c
building 'cy_script' extension
gcc-4.2 -fno-strict-aliasing -fno-common -dynamic -DNDEBUG -g -fwrapv -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE -arch i386 -arch ppc -arch x86_64 -pipe -I/System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6 -c cy_script.c -o build/temp.macosx-10.6-universal-2.6/cy_script.o
gcc-4.2 -Wl,-F. -bundle -undefined dynamic_lookup -arch i386 -arch ppc -arch x86_64 build/temp.macosx-10.6-universal-2.6/cy_script.o -lm -o cy_script.so

In the interpreter:

>>> import cy_script
1.41421356237
>>> cy_script.f(3)
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'module' object has no attribute 'f'
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'cy_script']
>>> from cy_script import *
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'cy_script']

Notice that following the first statement we get execution of print f(2); that is, cy_script was actually imported, but the names defined in cy_script are not available to us.

I have to look into more to see how to do this. What's the difference between the first example and the second one?
[UPDATE: Answer from Stack Overflow, as always!
Substitute cpdef double f(int i):, and it'll work. ]

[UPDATE2: There's an even cooler way to do this:

>>> import pyximport
>>> pyximport.install()
>>> import hello
>>> hello.say_hello_to('Tom')
Hello Tom!
>>> import cy_script
1.41421356237
>>> cy_script.f(3)
1.7320508075688772

Details here. ]