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

David Abrahams dave at boost-consulting.com
Fri Jan 13 14:52:36 CET 2006


Mathieu Lacage <Mathieu.Lacage at sophia.inria.fr> writes:

> On Thu, 2006-01-12 at 21:51 -0500, David Abrahams wrote:
>> > Okay, so, to wrap this, I wrapped my Simulator class and my EventWrap
>> > class:
>> > struct EventWrap : yans::Event
>> > {
>> >         EventWrap( PyObject* self_);
>> >         virtual ~EventWrap();
>> >         virtual void notify (void);
>> > private:
>> >         PyObject *m_self;
>> > };
>> > EventWrap::EventWrap (PyObject* self_)
>> >         : m_self(self_)
>> > {
>> >         Py_INCREF(m_self);
>> > }
>> > EventWrap::~EventWrap()
>> > {
>> >         Py_DECREF(m_self);
>> > }
>> 
>> This causes a double deletion.  Also, the Py_INCREF is somewhat evil
>
> I fail to see how it would cause a double deletion. 

~EventWrap is called because the event is being deleted.  It
decrements the refcount on the Python object that owns the very same
EventWrap object, which causes that Python object to be destroyed,
which causes it to attempt to destroy the EventWrap object it holds.

> It might create leaks in certain conditions if the c++ code does not
> call delete at some point but python should be forbidden from
> destroying this object because I hold a refcount to it.

You don't hold a refcount once you've Py_DECREF'd.

>> for other reasons.  In the Boost.Python object model, C++ objects are
>> owned by their Python wrappers, not vice-versa.  I think you'd be a
>
> Yes, but my c++ object needs to ensure that the python object is live
> when it calls back into it. If the Py_INCREF is not present, and if no
> python variable holds a reference to the original python object, boom
> when notify is called.

Use shared_ptr if you can.  Regardless, don't expect the 

>> lot better off if you stopped trafficking in raw pointers and used
>> boost::shared_ptr<Event> instead.
>
> Using shared_ptr would require my changing the c++ code to deal with
> shared_ptr rather than raw pointers, am I right ?

Yes.

>> > void
>> > EventWrap::notify (void)
>> > {
>> >         call_method<void>(m_self, "notify");
>> 
>> If this is related to your subject line, you should know that this
>> sort of old-style polymorphism does incur needless calls into Python.
>> Use derivation from boost::python::wrapper<Event> instead, with
>> get_override().
>
> Ah. This is good to know. I used this call_method way because I already
> had the private PyObject *m_self pointer because of the Py_INCREF so I
> thought I could avoid duplicating PyObject * everywhere (in the wrapper
> and in my code).
>
> However, I don't think the boost::python::wrapper way of dealing with
> this would completely eliminate the need to call back at least a bit
> into python, am I wrong ?

It depends on what you're doing, a clear picture of which I do not yet
have.  It eliminates the need to call back into Python when you call a
virtual function on a wrapped class instance and that virtual function
has *not* been overridden.

>> >         delete this;
>> 
>> There's that double-deletion again.
>
>
> I do not think so. The C++ code is designed such that the Simulator::run
> class does not deal with the memory management of the Event objects it
> calls into. That is, it merely assumes that the lifetime of the Event
> object is long enough to invoke Event::notify once and forget about it
> after. It is up to the Event::notify and event constructor to deal with
> the event's memory management. 

If the Python object is trying to deal with the event's lifetime
management simultaneously, you're going to have trouble. AFAICT
you haven't done anything to prevent the Python object from trying to
manage the lifetime of the Event.

> The c++ template make_event I use to
> generate event forwarding classes does this automatically. It could be
> argued that this is not very nice and I could even agree but, well.
>
> As such, the wrapper I presented in this email does something like this:
>   - python creates Event object which creates a c++ Event object
>   - python passes python Event object to simulator.insert_in_s which
> calls Simulator::insert_in_s which assumes ownership of the c++ Event *
> passed.

The reason shared_ptr works is that when you grab the shared_ptr, it
assumes ownership of the owning Python object and points to the C++
object.

>   - python calls simulator.run which calls into Simulator::run
>   - Simulator::run invokes c++ Event::notify which invokes back
> Event.notify into python.
>   - Event::notify deletes c++ object and should delete associated python
> object.
>
> I fail to see how the end-user API I want to offer could be implemented
> in another way without changing the wrapped c++ code.

Hold the EventWrap with an auto_ptr.  In its destructor, do
extract<auto_ptr<EventWrap>&>(self)().reset() before the
Py_DECREF(self).  Then you've at least broken the ownership cycle,
other evils notwithstanding.

> Changing the c++ code would require one of:
>   - as suggested by you, using shared_ptr in the c++ code
>   - Another solution might be to make the memory management issue
> explicit at the C++ API level through some other mean than shared_ptr.
> Adding a pair of ref/unref methods on the Event c++ class for example...
>
>> > which allows me to write the following python code:
>> > class __MyEvent__(Event):
>> 
>> Bad idea to name your symbols that way; __XXX__ symbols are by
>> convention reserved to the Python language.
>
> ok.
>
>> 
>> >     def set_callback (self, function, *args):
>> >         self.__function_ = function;
>> >         self.__args_ = args;
>> >     def notify (self):
>> >         self.__function_ (*self.__args_);
>> >
>> > def make_event(function, *args):
>> >     ev = __MyEvent__ ();
>> >     ev.set_callback (function, *args);
>> >     return ev
>> >
>> > def my_nothing0 ():
>> >     print "my model now=%f" % simulator.now_s ();
>> > simulator.insert_in_s (4.0, make_event (my_nothing1, 99));
>> > simulator.run ()
>> >
>> >
>> > okay, so all of this stuff works really nicely but there is a catch. I
>> > would like to be able to write code such as:
>> >
>> > simulator.insert_in_s (4.0, make_event (simulator.now_s));
>> > simulator.run ()
>> >
>> > and ensure that once simulator.run has reached the Simulator::run c++
>> > method, no python code will be executed ever. Namely, I want call to
>> > make_event to generate a nice C++ Event class which will not call back
>> > into python's __MyEvent__.notify before calling simulator.now_s and
>> > Simulator::now_s. i.e., it should call back Simulator::now_s directly.
>> > 
>> > The more I think about this, the less I see a way to do this with boost.
>> > Have I missed something ?
>> 
>> Use new-style polymorphism.
>
> "new style polymorphism" is the use of get_override ? If so, you mean
> that it can be made to call into c++ if python does not override the
> function, right ?

Right.

> Something like this ?
>
> struct ACallback :  A, wrapper<A>
> {
>     char const* f()
>     {
>         if (override f = this->get_override("f"))
>             return f();
>         return A::f();
>     }
>
>     char const* default_f() { return this->A::f(); }
> };
>
> I don't think this is what I want to do. I would like to make python's
> make_event function create the right underlying c++ Event class which,
> rather than trying to call back into python does not bother with it and
> just calls into c++ directly.
>
> In case my description of my use-case is not very clear, here is the
> real code for the c++ simulator:

Sorry, I don't have time right now to look into your case in that kind
of detail. :(

With Regret,
-- 
Dave Abrahams
Boost Consulting
www.boost-consulting.com




More information about the Cplusplus-sig mailing list