[C++-sig] optimizing away calls to the python runtime -- was [detecting if a function is written in python or C++]

François Duranleau duranlef at iro.umontreal.ca
Tue Jan 17 22:29:27 CET 2006


On Tue, 17 Jan 2006, David Abrahams wrote:

> Mathieu Lacage <Mathieu.Lacage at sophia.inria.fr> writes:
>
>> My wrapped c++ code is not going to use boost::function but a similar
>> template-based thing which will end up generating classes such as
>> Callback1 in my example.
>
> Why reinvent the wheel?  boost::function will be more efficient,
> better tested, etc...

I have been following this discussion with great interest, since I am 
facing similar problems. I have some function templates to expose to 
Python and some of their template arguments are functions (functor or
function pointers), e.g. (dummy example):

template < typename Func >
void do_something( Func f )
{
     for ( int i = 0 ; i < 1000000 ; ++ i )
     {
         f( 10 ) ;
     }
}


The easiest way to expose it is, I think, something like this:

def( "do_something" , & do_something< object > ) ;


However, as pointed out by the timings given by Mathieu's last test, if 
the function parameter is to be called very often, it's a hit on 
performance. It could be possible to do something like the following, 
using boost::function :

typedef boost::function< void ( int ) > function_type ;

def( "do_something" , & do_something< function_type > ) ;


and then for each exposed functor, e.g.:

struct functor
{
     void operator () ( int a ) const
     {
         // ...
     }
} ;


you can use boost::python::implicitly_convertible:

class_< functor >( "Functor" )
     .def( "__call__" , & functor::operator () )
     ;

implicitly_convertible< functor , function_type >() ;


However, with this, you can't use Python callable objects anymore, unless 
we add an explicit converter to function_type for those:

function_type make_function( object o )
{
     return function_type( o ) ;
}


and then:

class_< function_type >( "Function" ) ;

def( "make_function" , & ::make_function ) ;


So in Python, you can do something like this:

// import the stuff

def func( a ) :
     # ...
     pass

do_something( Functor() )

do_something( make_function( func ) )


However, if we define a C++ function and want to pass it to do_something 
via Python, it seems like we have to either use make_function (and lose 
performance), or define a C++ wrapper functor. But in any case, the user 
has to explicitly use make_function for Python (actually, any other 
callable types not defined in your Boost.Python code) callable objects, 
and this non-uniformity between each type of call is not intuitive: a user 
must know if an object comes from Python or C++. Well, he could put 
make_function's everywhere, but he would lose the benefit. The ultimate 
goal would be to avoid any 'make_function'-like calls.

I put the complete example here (Makefile, test2.cc, test2.py):
http://www-etud.iro.umontreal.ca/~duranlef/python/

Actually, the whole point is, even using boost::function, it seems like it 
just moves the problem to someplace else, that is, how can you extract a 
C++ function/functor of unknown type (just the function signature is 
known) to store in the boost::function object, and otherwise (not a C++ 
object) just store the Python object, all that in a nice and easy way (for 
the user, at least)? Mathieu found a way, but it relies on some kind of 
hack with C++ name patterns. As a matter of fact, we could also use a hack 
with the __class__ attribute (it seems like the value for all Boost.Python 
classes is <type 'Boost.Python.class'>. But what about C++ functions? Are 
we doomed to have to deal with wrapper functors?

-- 
François Duranleau
LIGUM, Université de Montréal

"There are as many truths as there are people."
                                           - from _Neon Genesis Evangelion_


More information about the Cplusplus-sig mailing list