slow down when passing functions

40 views
Skip to first unread message

Alexander Papageorge

unread,
Jul 21, 2021, 3:48:49 PM7/21/21
to cython-users
I have a very simple timing example i don't understand - I pass a function and call it, but it is much slower in cython:


import time

cdef void func0():
    1 is 1

cdef func1():
    1 is 1

ctypedef void (*f_type)()

cdef int ITERS = 100000000

cdef double do_timing(f_type f):
    cdef int i
    now = time.time()
    for 0 <= i < ITERS:
        f()
    return time.time() - now

cdef void test0():
    print(do_timing(func0))

def test1():
    cdef int i
    now = time.time()
    for 0 <= i < ITERS:
        func1()

print(time.time() - now)

test0()
test1()

test1 is almost 10x faster than test0. What am I doing wrong? Thank you in advance.

Stefan Behnel

unread,
Jul 21, 2021, 3:53:30 PM7/21/21
to cython...@googlegroups.com
Alexander Papageorge schrieb am 21.07.21 um 21:36:
None of this is probably doing what you think it does. Since your test
functions are not actually doing anything, any decent C compiler would
optimise their usage away, and then also the useless loop around their call.

My guess is that the difference comes from the additional call to
time.time() at the end of do_timing().

Stefan

Alexander Papageorge

unread,
Jul 21, 2021, 4:10:23 PM7/21/21
to cython...@googlegroups.com
this is very helpful, thank you. let me keep investigating.

--

---
You received this message because you are subscribed to the Google Groups "cython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cython-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/cython-users/451b9103-79f0-57ba-5814-772de8eceb6f%40behnel.de.

Alexander Papageorge

unread,
Jul 21, 2021, 4:10:49 PM7/21/21
to cython...@googlegroups.com
I modified it to avoid (?) some of these concerns, and cython is still 3x slower

import time

cdef void func0(int x):

    1 == x

cdef func1(x):

    1 == x

ctypedef void (*f_type)(int)

cdef int ITERS = 100000000

cdef void do_timing(f_type f):

    cdef int i

    for 0 <= i < ITERS:

        f(1)

cdef void test0():

    now = time.time()

    do_timing(func0)

    print(time.time() - now)

def test1():

    cdef int i

    now = time.time()

    for 0 <= i < ITERS:

        func1(1)

    print(time.time() - now)

test0()

test1()

Stefan Behnel

unread,
Jul 21, 2021, 4:18:45 PM7/21/21
to cython...@googlegroups.com
Alexander Papageorge schrieb am 21.07.21 um 22:10:
> I modified it to avoid (?) some of these concerns, and cython is still 3x
> slower

You're probably still comparing apples with tomatoes.

I recommend benchmarking your actual code, not some artificial micro
benchmarks. Getting micro benchmarks right is even harder in C than in Python.

Stefan

Alexander Papageorge

unread,
Jul 21, 2021, 4:19:00 PM7/21/21
to cython...@googlegroups.com
all else being equal, i can create a 'slowdown' by changing the function signature from cdef to cdef void.

Alexander Papageorge

unread,
Jul 21, 2021, 4:56:16 PM7/21/21
to cython...@googlegroups.com
ok, that's totally reasonable - can i ask - what do you recommend re passing functions in cython? is the form of the code i wrote correct? are there other declarations or enhancements i'm missing?

--

---
You received this message because you are subscribed to the Google Groups "cython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cython-users...@googlegroups.com.

D Woods

unread,
Jul 23, 2021, 11:48:35 AM7/23/21
to cython-users
> all else being equal, i can create a 'slowdown' by changing the function signature from cdef to cdef void.

That may cause a change in exception handling - `cdef void` will either have to swallow exceptions internally or always check if an exception is set after it's called. `cdef` will return a `PyObject*` which can be checked against NULL. I'm not sure exactly what difference this will make to performance, but it's a larger change than you think. You can set the exception specification to try to control it.

> what do you recommend re passing functions in cython? is the form of the code i wrote correct? are there other declarations or enhancements i'm missing?

What you're doing is right - a C function pointer is the fastest way of passing around functions. However it is (and always will be) slower than a direct function call because the C compiler can't "see inside" the function it's calling. You can inspect the generated C code to find out if Cython is doing things like exception checks that you don't need though.

Alexander Papageorge

unread,
Jul 23, 2021, 2:15:31 PM7/23/21
to cython...@googlegroups.com
This very helpful, thank you very much!

PY C.

unread,
Nov 17, 2021, 6:42:20 AM11/17/21
to cython-users
Have you tried running with different machines, compilers, etc., and also the pre-release version of Cython (e.g., cython-v3.0.0a9)? On my machine, test0 takes a trivial time and is much much much much faster than test1.
Reply all
Reply to author
Forward
0 new messages