I ran another benchmark showing the opposite case: a single call to a long
running c function. The results are surprising, Cython is much much faster.
(this may be do my inability to compile an optimized dll).

Anyway, the following is the code I tested:


##### Cython script (.pyx) #########

cdef double count(int n) nogil:
    cdef int i, j, k
    cdef double out
    for i from 0 <= i < n:
        for j from 0 <= j < n:
            out = 0
            for k from 0 <= k < n:
                out += k

    return out

def myinterface(int a):
    cdef int n = a
    cdef double out
    with nogil:
        out = count(n)
    return out

####### Ctypes scripts (pure .c) #########

#include "ctypesgiltest.h"

EXPORT double count(int n){
    int i, j, k;
    int a, b, c;
    double out;

    a = n;
    for (i=0; i<a; i++){
        b = n;
        for (j=0; j<b; j++){
            out = 0;
            c = n;
            for (k=0; k<c; k++){
                out += k;
            }
        }
    }

    return out;
}



/******** giltest.h ***********/
#ifdef BUILD_DLL

#define EXPORT __declspec(dllexport)
#else

#define EXPORT __declspec(dllimport)
#endif

EXPORT double count(int);


####### python script testing the two functions ########
import threading
import time
from giltest import myinterface
from ctypes import *

class NoGil(threading.Thread):

    def __init__(self, id):
        super(NoGil, self).__init__()
        self.id = id

    def run(self):
        t1 = time.clock()
        a = myinterface(2000)
        secs = time.clock() - t1
        print ('no GIL %s: ' % self.id), ('Value: %f' % a), ('Time: %f s' %
secs)

class NoGilCtypes(threading.Thread):

    def __init__(self, id):
        super(NoGilCtypes, self).__init__()
        self.id = id
        self.count = cdll.ctypesgiltest.count
        self.count.restype = c_double

    def run(self):
        t1 = time.clock()
        a = self.count(c_int(2000))
        secs = time.clock() - t1
        print ('no GIL %s: ' % self.id), ('Value: %f' % a), ('Time: %f s' %
secs)


if __name__=='__main__':
    a = NoGil('cython')
    b  = NoGilCtypes('ctypes')

    print ('Starting Threads')
    a.start()
    b.start()





on my machine Q9650  Win Vista, this uses the expected 2 cores during
execution.

The results are:

>>>
Starting Threads
>>> no GIL cython:  Value: 1999000.000000 Time: 8.076570 s
no GIL ctypes:  Value: 1999000.000000 Time: 38.927630 s


That's a BIG advantage to Cython! Kudos to the developers.

Looking into the Cython generated code, the two c files are virtually
identical. I assume the performance is down to compiler optimization.

Using minGW-32, I issued the following commands to build the DLL for ctypes:

>gcc -c -DBUILD_DLL ctypesgiltest.c
>gcc -shared -o ctypesgiltest.dll ctypesgiltest.o




Chris




On Wed, May 13, 2009 at 4:53 AM, Robert Bradshaw <
[email protected]> wrote:

> On May 13, 2009, at 1:50 AM, Sebastien Binet wrote:
>
> > On Wednesday 13 May 2009 10:41:43 Robert Bradshaw wrote:
> >> On May 13, 2009, at 1:17 AM, Sebastien Binet wrote:
> >>> hi,
> >>>
> >>> On Wednesday 13 May 2009 08:35:27 Robert Bradshaw wrote:
> >>>> On May 12, 2009, at 9:47 PM, Mohamed Lrhazi wrote:
> >>>>> On Wed, May 13, 2009 at 12:31 AM, Chris Colbert
> >>>>>
> >>>>> <[email protected]> wrote:
> >>>>>> If your making lots of rapid calls to short running functions in
> >>>>>> the
> >>>>>> C-library, then you may start to feel the ctypes overhead.
> >>>>>
> >>>>> That's what I was afraid to hear..
> >>>>
> >>>> Hopefully after using Cython a bit, you're fears will quickly go
> >>>> away :).
> >>>>
> >>>>> What I was hoping to hear is "Oh
> >>>>> no, ctypes is all C anyways, and will perform just the same as
> >>>>> Cython"
> >>>>
> >>>> A simple benchmark:
> >>>>
> >>>> import ctypes
> >>>> libm = ctypes.cdll.LoadLibrary("libm.dylib") # platform
> >>>> dependent...
> >>>> def ctypes_sum(N):
> >>>>      lib_sqrt = libm.sqrt
> >>>>      lib_sqrt.argtypes = (ctypes.c_double,)
> >>>>      lib_sqrt.restype = ctypes.c_double
> >>>>      s = 0
> >>>>      for i in range(N):
> >>>>          s += lib_sqrt(i)
> >>>>      return s
> >>>>
> >>>> %cython
> >>>> cdef extern from "math.h":
> >>>>      double sqrt(double)
> >>>>
> >>>> def cython_sum(long N):
> >>>>      cdef int i
> >>>>      cdef double s=0
> >>>>      for i in range(N):
> >>>>          s += sqrt(i)
> >>>>      return s
> >>>>
> >>>>>>> time ctypes_sum(10**6)
> >>>>
> >>>> 666666166.4588418
> >>>> Time: CPU 1.13 s, Wall: 1.14 s
> >>>>
> >>>> time cython_sum(10**6)
> >>>> 666666166.4588418
> >>>> Time: CPU 0.03 s, Wall: 0.03 s
> >>>
> >>> interesting simple minded benchmark :)
> >>>
> >>> how would this translate into the pure-python mode ?
> >>
> >> Pure Python:
> >>
> >> def python_sum(N):
> >>      from math import sqrt
> >>      s = 0
> >>      for i in range(N):
> >>          s += sqrt(i)
> >>      return s
> >>
> >>>>> time python_sum(10**6)
> >>
> >> 666666166.4588418
> >> Time: CPU 0.47 s, Wall: 0.47 s
> >>
> >> So for such a tiny call, ctypes is slower. (Since math.sqrt is a
> >> static wrapper around libm's sqrt, no surprise here.)
> >>
> >>> (I couldn't seem to be
> >>> able to declare the C-sqrt function using the pure-python mode of
> >>> cython:
> >>> http://wiki.cython.org/pure wasn't helpful)
> >>
> >> The pure-python mode should be exactly the same as the above, but
> >> there's no way to declare functions in it (yet).
> > but if you pyximport import it, you should get the performance back
> > (assuming
> > the function declaration were here), right ?
>
> Yes, that's right.
>
> There'd have to be some way of specifying "here's a C function, but
> in pure python mode, use this python function instead."
>
> - Robert
>
> _______________________________________________
> Cython-dev mailing list
> [email protected]
> http://codespeak.net/mailman/listinfo/cython-dev
>
_______________________________________________
Cython-dev mailing list
[email protected]
http://codespeak.net/mailman/listinfo/cython-dev

Reply via email to