Speed of Cffi vs ctypes in cpython?

1,506 views
Skip to first unread message

Victor Blomqvist

unread,
Sep 25, 2016, 1:23:26 AM9/25/16
to python-cffi
Hi,

I have a library (pymunk) which I recently updated to use cffi in API mode instead of ctypes. I ran some benchmarks I and from my testing it turns out that the new version using cffi is much slower than the old one using ctypes on CPython (on Pypy cffi is faster). Im interested in if its possible to speed up the CPython part.

Pymunk is a physics library, and the most common operation done is to read out a object's 2d position so this is what I have been testing. I guess the slowdown comes from the wrapping done with cffi, since I create a new instance of my 2d vector class each time.

The c library has these defined:

    typedef struct cpVect{cpFloat x,y;} cpVect;
    typedef struct cpBody cpBody;
    cpVect cpBodyGetPosition(const cpBody *body);

In the ctypes version I have a wrapper class:

class Vec2d(ctypes.Structure):
    __slots__ = ['x', 'y']
    @classmethod
    def from_param(cls, arg):
        return cls(arg)
   
Then the python code will automatically convert/wrap the returned position with the Vec2d.

In the cffi version its different since I cant inherit the struct:

class Vec2d(list):
    __slots__ = ()
       
    def __init__(self, x_or_pair=None, y = None):
        if x_or_pair != None:
            if y == None:
                if hasattr(x_or_pair, "x") and hasattr(x_or_pair, "y"):
                    super(Vec2d, self).__init__([x_or_pair.x, x_or_pair.y])

Then the position is returned with Vec2d(lib.cpBodyGetPosition( ... ))

Could I do this in some smarter / faster way? I still want a Vec2d returned, so the user have a full Vec2d object that can be used for further calculations.

Thanks!
Victor

Armin Rigo

unread,
Sep 25, 2016, 3:08:44 AM9/25/16
to pytho...@googlegroups.com
Hi,

On 25 September 2016 at 07:23, Victor Blomqvist <v...@viblo.se> wrote:
> class Vec2d(list):
> __slots__ = ()
>
> def __init__(self, x_or_pair=None, y = None):
> if x_or_pair != None:
> if y == None:
> if hasattr(x_or_pair, "x") and hasattr(x_or_pair, "y"):
> super(Vec2d, self).__init__([x_or_pair.x, x_or_pair.y])
>
> Then the position is returned with Vec2d(lib.cpBodyGetPosition( ... ))

How about not abusing the constructor to convert a cffi struct to a
Vec2d? Also, how about not inheriting from "list":

class Vec2d(object):
def __init__(self, x, y):
self.x = x
self.y = y

def wrap(my_struct):
return Vec2d(my_struct.x, my_struct.y)

v = wrap(lib.cpBodyGetPosition(...))

It's probably faster. If you want extra speed you can probably inline
Vec2d.__init__() manually:

def wrap(my_struct, _new=Vec2d.__new__):
v = _new(Vec2d)
v.x = my_struct.x
v.y = my_struct.y
return v


A bientôt,

Armin.

Victor Blomqvist

unread,
Sep 26, 2016, 10:09:46 AM9/26/16
to pytho...@googlegroups.com
All of those things helps quite a bit. My first code ran 4.3x slower than the ctypes version, by ditching the list its 2.1x slower, and by using __new__ 1.9x slower. Still a lot slower than ctypes, but at least a big improvement! If I dont use my python Vec2d at all and instead just pass around the cdata its still 1.5x slower, so 1.9x should be a reasonable number I guess. Are there any specific reasons why cffi is slower? Seems like quite a big difference compared to ctypes?

The reason I inherited from list in the first place is actually Cffi, since when I pass in the Vec2d to a function accepting the c struct it needs to be list, tuple, dict or cdata, or it will give this exception: "TypeError: initializer for ctype 'cpVect' must be a list or tuple or dict or struct-cdata, not Vec2d". So I thought I would make my life a bit easier and not have a "unwrap" method to call every time I need to pass in my Vec2d to the c function. A (ugly?) thing I tried just now is to inherit from list but still use __new__ without calling super (and overriding all the __getitem__ and so on which I need to to anyway if I dont inherit list. This brings down the time to 2.5x slower, which is not that bad compared to 1.9x.


Thanks,
Victor



--
-- python-cffi: To unsubscribe from this group, send email to python-cffi+unsubscribe@googlegroups.com. For more options, visit this group at https://groups.google.com/d/forum/python-cffi?hl=en
---
You received this message because you are subscribed to a topic in the Google Groups "python-cffi" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/python-cffi/FIlpKI6fBm4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to python-cffi+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Armin Rigo

unread,
Sep 26, 2016, 2:32:19 PM9/26/16
to pytho...@googlegroups.com
Hi Victor,

On 26 September 2016 at 16:09, Victor Blomqvist <v...@viblo.se> wrote:
> All of those things helps quite a bit. My first code ran 4.3x slower than
> the ctypes version, by ditching the list its 2.1x slower, and by using
> __new__ 1.9x slower. Still a lot slower than ctypes, but at least a big
> improvement! If I dont use my python Vec2d at all and instead just pass
> around the cdata its still 1.5x slower, so 1.9x should be a reasonable
> number I guess. Are there any specific reasons why cffi is slower? Seems
> like quite a big difference compared to ctypes?

The next thing to tweak would be not have C functions that return
structs. This involves slower paths through the code, both in CPython
and in PyPy. I have no real clue if it's going to be faster or slower
on CPython, because it involves a bit more pure Python code; you need
to try. Fwiw it should definitely be faster on PyPy. For example:

typedef struct cpVect{cpFloat x,y;} cpVect;
typedef struct cpBody cpBody;
void cpBodyGetPosition(const cpBody *body, cpVect *result);

class Body(object):
...
def get_position(self):
vec = ffi.new("cpVect *")
lib.cpBodyGetPosition(self._body, vec)
return Vec2d(vec.x, vec.y)


Note that in general, getting the best possible CPython performance is
not high on the list of priorities. I prefer not to introduce 27
hints like "don't release the GIL when calling this function here".


A bientôt,

Armin.

Victor Blomqvist

unread,
Sep 27, 2016, 9:19:18 AM9/27/16
to pytho...@googlegroups.com
Understandable. Are there any (big) differences between ABI and API
mode (both on CPython and Pypy)?

Thanks for the input so far!
Victor

Armin Rigo

unread,
Oct 1, 2016, 3:17:00 AM10/1/16
to pytho...@googlegroups.com
Hi Victor,

On 27 September 2016 at 15:19, Victor Blomqvist <v...@viblo.se> wrote:
> Understandable. Are there any (big) differences between ABI and API
> mode (both on CPython and Pypy)?

You have to measure your particular use case. In some cases there are
differences, and in other cases not.


A bientôt,

Armin.
Reply all
Reply to author
Forward
0 new messages