newbie question on function assignment

47 views
Skip to first unread message

paomo

unread,
Jan 31, 2012, 4:12:16 PM1/31/12
to cython-users
I would like to assign two cython functions to a dictionary, so they
can be selected by the dictionary key, as in:

cdef f1():
...
cdef f2():
...
my_functions = {'case_1' : f1,
'case_2' : f2} # this won't compile
my_functions['case_1']()

but this won't compile, because cython tries to convert f1 and f2 in
the dictionary to python objects and fails. Is there a work around
for this?

Thanks

Robert Bradshaw

unread,
Feb 2, 2012, 2:47:34 PM2/2/12
to cython...@googlegroups.com

Either use standard python "def" functions rather than cdef functions,
or index into an array of function pointers with enums.

- Robert

Ethan Furman

unread,
Feb 4, 2012, 3:53:57 PM2/4/12
to cython...@googlegroups.com
Robert Bradshaw wrote:

> On Tue, Jan 31, 2012 at 1:12 PM, paomo wrote:
>> cdef f1():
>> ...
>> cdef f2():
>> ...
>> my_functions = {'case_1' : f1,
>> 'case_2' : f2} # this won't compile
>> my_functions['case_1']()
>>
>> but this won't compile, because cython tries to convert f1 and f2 in
>> the dictionary to python objects and fails. Is there a work around
>> for this?
>
> Either use standard python "def" functions rather than cdef functions,
> or index into an array of function pointers with enums.

Apologies for my ignorance, but could you show an example of an array of
funtion pointers and their use with enums?

~Ethan~

mark florisson

unread,
Feb 6, 2012, 4:03:55 PM2/6/12
to cython...@googlegroups.com

If you don't know what it means, you probably don't want it :) Is
there any reason you're not using def functions?

Ethan Furman

unread,
Feb 6, 2012, 6:33:12 PM2/6/12
to cython...@googlegroups.com
mark florisson wrote:

> On 4 February 2012 20:53, Ethan Furman wrote:
>> Robert Bradshaw wrote:
>>> On Tue, Jan 31, 2012 at 1:12 PM, paomo wrote:
>>>> cdef f1():
>>>> ...
>>>> cdef f2():
>>>> ...
>>>> my_functions = {'case_1' : f1,
>>>> 'case_2' : f2} # this won't compile
>>>> my_functions['case_1']()
>>>
>>> Either use standard python "def" functions rather than cdef functions,
>>> or index into an array of function pointers with enums.
>>
>> Apologies for my ignorance, but could you show an example of an array of
>> funtion pointers and their use with enums?
>
> If you don't know what it means, you probably don't want it :) Is
> there any reason you're not using def functions?

At this point I am still gathering knowledge. I hope to write a Cython
version of my dbf module at some point.

~Ethan~

Robert Bradshaw

unread,
Feb 8, 2012, 5:48:41 AM2/8/12
to cython...@googlegroups.com
On Mon, Feb 6, 2012 at 3:33 PM, Ethan Furman <et...@stoneleaf.us> wrote:
> mark florisson wrote:
>>
>> On 4 February 2012 20:53, Ethan Furman wrote:
>>>
>>> Robert Bradshaw wrote:
>>>>
>>>> On Tue, Jan 31, 2012 at 1:12 PM, paomo wrote:
>>>>>
>>>>> cdef f1():
>>>>>  ...
>>>>> cdef f2():
>>>>>  ...
>>>>> my_functions = {'case_1' : f1,
>>>>>               'case_2' : f2}   # this won't compile
>>>>> my_functions['case_1']()
>>>>
>>>>
>>>> Either use standard python "def" functions rather than cdef functions,
>>>> or index into an array of function pointers with enums.
>>>
>>>
>>> Apologies for my ignorance, but could you show an example of an array of
>>> funtion pointers and their use with enums?
>>
>>
>> If you don't know what it means, you probably don't want it :) Is
>> there any reason you're not using def functions?

I'll second Mark's point.

> At this point I am still gathering knowledge.  I hope to write a Cython
> version of my dbf module at some point.

Best of luck; let us know if you run into any more blockers.

- Robert

Aronne Merrelli

unread,
Feb 8, 2012, 12:10:31 PM2/8/12
to cython...@googlegroups.com
On Wed, Feb 8, 2012 at 4:48 AM, Robert Bradshaw <robe...@math.washington.edu> wrote:
On Mon, Feb 6, 2012 at 3:33 PM, Ethan Furman <et...@stoneleaf.us> wrote:
> mark florisson wrote:
>>
>> On 4 February 2012 20:53, Ethan Furman wrote:
>>>
>>> Robert Bradshaw wrote:
>>>>
>>>> On Tue, Jan 31, 2012 at 1:12 PM, paomo wrote:
>>>>>
>>>>> cdef f1():
>>>>>  ...
>>>>> cdef f2():
>>>>>  ...
>>>>> my_functions = {'case_1' : f1,
>>>>>               'case_2' : f2}   # this won't compile
>>>>> my_functions['case_1']()
>>>>
>>>>
>>>> Either use standard python "def" functions rather than cdef functions,
>>>> or index into an array of function pointers with enums.
>>>
>>>
>>> Apologies for my ignorance, but could you show an example of an array of
>>> funtion pointers and their use with enums?
>>
>>
>> If you don't know what it means, you probably don't want it :) Is
>> there any reason you're not using def functions?

I'll second Mark's point.


I'm trying to do this right now - it actually doesn't seem like an unusual use case. Inside the innermost loop I am doing a calculation that I now would like to change depending on the simulation settings. So it is imperative to use cdef functions. I tried several implementations but these have been unsatisfying. I tried what I thought was meant by 'function pointer array' with the following code (I'm a C newbie so I might be way off here...):

cdef double basefunc0(double x):
    return x*x
cdef double basefunc1(double x):
    return x*x*x
cdef double (*funcList[2])(double)
funcList[0] = &basefunc0
funcList[1] = &basefunc1

The cython function that uses these functions looks like:

def cy_calc_fptr(double x, int selected_func):
    ...
    theResult = funcList[selected_func](x)
    ...

It compiles, and I get the expected ouput. However this is still 3 orders of magnitude slower than the following ugly hard coded implementation:

def cy_ref_calc2(double x, double y, int function_id):
    ...
    if function_id == 0:
        theResult = basefunc0(x)
    else:
        theResult = basefunc1(x)
    ...

The cython -a results show this as being "direct" C code; so it would seem that using the function pointer causes a large overheard even in C, which is unexpected. Am I missing something or is that just the way it works? I posted the full pyx file as a gist since it was getting a bit long (I also tried cdef classes, but that was about a factor of 4-5 slower than function pointer).

https://gist.github.com/1771057


Thanks,
Aronne

Robert Bradshaw

unread,
Feb 8, 2012, 6:26:52 PM2/8/12
to cython...@googlegroups.com
n Wed, Feb 8, 2012 at 9:10 AM, Aronne Merrelli

OK, looks like you know what you're doing :). Yes, this is what I meant.

> The cython function that uses these functions looks like:
>
> def cy_calc_fptr(double x, int selected_func):
>     ...
>     theResult = funcList[selected_func](x)
>     ...
>
> It compiles, and I get the expected ouput. However this is still 3 orders of
> magnitude slower than the following ugly hard coded implementation:
>
> def cy_ref_calc2(double x, double y, int function_id):
>     ...
>     if function_id == 0:
>         theResult = basefunc0(x)
>     else:
>         theResult = basefunc1(x)
>     ...
>
> The cython -a results show this as being "direct" C code; so it would seem
> that using the function pointer causes a large overheard even in C, which is
> unexpected. Am I missing something or is that just the way it works?

I think that's just the way it is. Function pointers require an extra
level of indirection (as well as missing out on a number of
optimizations that could be performed after inlining such a short
function, which is probably the larger issue at play here).

Aronne Merrelli

unread,
Feb 9, 2012, 4:34:46 PM2/9/12
to cython...@googlegroups.com


On Wed, Feb 8, 2012 at 5:26 PM, Robert Bradshaw <robe...@math.washington.edu> wrote:
> The cython function that uses these functions looks like:
>
> def cy_calc_fptr(double x, int selected_func):
>     ...
>     theResult = funcList[selected_func](x)
>     ...
>
> It compiles, and I get the expected ouput. However this is still 3 orders of
> magnitude slower than the following ugly hard coded implementation:
>
> def cy_ref_calc2(double x, double y, int function_id):
>     ...
>     if function_id == 0:
>         theResult = basefunc0(x)
>     else:
>         theResult = basefunc1(x)
>     ...
>
> The cython -a results show this as being "direct" C code; so it would seem
> that using the function pointer causes a large overheard even in C, which is
> unexpected. Am I missing something or is that just the way it works?


I think that's just the way it is. Function pointers require an extra
level of indirection (as well as missing out on a number of
optimizations that could be performed after inlining such a short
function, which is probably the larger issue at play here).


 
Ah, yes, I looked at this again today and see that I was badly skewing the results because the test function is too simple. The "ugly hard coded" implementation does not change anything inside the loop, so I think the compiler was actually optimizing this by replacing the loop with a single operation (maybe that is a consequence of "inlining"?). It wasn't doing the same thing to the function pointer variant, so comparing the timing of the two was meaningless. By changing the loop to:

theResult = funcList[selected_func](x + n*1e-7)

the compiler cannot skip it. Now the different methods compare much more as expected. I'm just using pyximport to run this, so I don't think it uses optimization flags, but in this case I can't measure any overhead at all for the function pointer variant. The Cython cdef class is the only method that appears to have any overhead, and it is fairly modest at only about 30%.

Sorry for the confusion!

Thanks,
Aronne


Robert Bradshaw

unread,
Feb 9, 2012, 7:03:49 PM2/9/12
to cython...@googlegroups.com

Was that with a cdef or cpdef method? It's just using a virtual
function table, so I'd hope there isn't additional overhead. (Well, I
suppose it's two defererences rather than one, but unless your
function is trivial that should be in the noise...)

> Sorry for the confusion!

No problem. Sounds like you're making a lot of progress (and it's good
to have this kind of thread documented for future users as well).

- Robert

Aronne Merrelli

unread,
Feb 10, 2012, 1:43:27 AM2/10/12
to cython...@googlegroups.com

I haven't started work on my "real" code yet, so yes it is a trivial function for this test (return x*x). It was a cdef class with cpdef methods, basically copied from:
http://docs.cython.org/src/tutorial/cdef_classes.html

The full pyx is here: https://gist.github.com/1771057

The timings are still mysterious to me - I made some further changes that I thought were trivial (the loop length is now an input to the function), but produced a  significant impact. But all variants of the cython code appear to be within a factor of 2 of the hardcoded cython cdef function, which is good enough for me.

Thanks,
Aronne
Reply all
Reply to author
Forward
0 new messages