[Controller] POC: overall speedup#573
Conversation
alxbilger
left a comment
There was a problem hiding this comment.
Is the cache system also relevant for other trampoline classes?
To my meager knowledge, I would say yes. But in this case it is especially useful because there were(are) many lookups to call implicitly all the *event() every step. |
57debb4 to
040d3e6
Compare
|
Nice cache mechanism. Now I wonder what would happend if someone changed dynamically the method after it has already been cached : MyController.onAnimateBeginEvent() # Cache is now active for this method
MyController.onAnimateBeginEvent = randomMethod # Cache is now invalid
MyController.onAnimateBeginEvent() # now what's called ? Maybe we could add a setattr overloead invalidating the cache when it is called on a method already cached ? |
no idea, I even did not know you could change class method dynamically like that 🙃 |
040d3e6 to
63b1a8d
Compare
|
@damienmarchal your review would be most valuable here 🙏 |
@bakpaul Claude judged you a little bit : but his suggestion is similar to yours. |
|
The new commit manages the reassignment, and I add a test to check (https://github.com/fredroy/SofaPython3/blob/492512136c3ab47b2665931b241d52a44a260c02/examples/tests/testReassignmentController.py) after it prints: |
I will... nice PR. |
4925121 to
0cb5d02
Compare
|
@damienmarchal, still waiting for your review |
|
OMG we are thursady, thank for pointing the pending review. |
| if (f_printLog.getValue()) | ||
| { | ||
| std::string eventStr = py::str(PythonFactory::toPython(event)); | ||
| msg_info() << "on" << event->getClassName() << " " << eventStr; | ||
| } |
There was a problem hiding this comment.
As msg_info() already contains f_printLog.getValue() test this should be implemented with one-liner more or less like:
msg_info() << "on" << event->getClassName() << " " << py::cast<std::string>(py::str(PythonFactory::toPython(event)));I'm a bit puzzled by the conversion chain from event to python, to str, to string. Cannot we have a direct c++ event to std::string ?
There was a problem hiding this comment.
As msg_info() already contains f_printLog.getValue() test this should be implemented with one-liner more or less like:
msg_info() << "on" << event->getClassName() << " " << py::cast<std::string>(py::str(PythonFactory::toPython(event)));
but even if the msg_info() is not printed, py::cast<std::string>(py::str(PythonFactory::toPython(event)) will still be executed (for nothing as we dont want the print) no ?
I'm a bit puzzled by the conversion chain from event to python, to str, to string. Cannot we have a direct c++ event to std::string ?
AFAIK (but I may be wrong) there is no serialization of an Event in Sofa C++ so it needs to be done first.
There was a problem hiding this comment.
I'm not sure the cast part will be executed because the msg_info() has been specifically implemented so that the right side of conditional on-liners messages cost nothing in case the condition is false.
So unless a makes a mistake, the code you are pointing is actually equivalent to:
if( sofa::helper::logging::notMuted(this) ) // Test the condition and if true create the message and << into it...
sofa::helper::logging::MessageDispatcher::info(sofa::helper::logging::Message::Runtime, sofa::helper::logging::getComponentInfo(emitter), SOFA_FILE_INFO) << "on" << event->getClassName() << " " << py::cast<std::string>(py::str(PythonFactory::toPython(event)));Side note: I would vote for event serialization in c++ ;)
|
@bakpaul thank for poking for review. Done now. |
Summary of Modifications
The changes in this branch (speedup_controller) optimize the Controller_Trampoline class in the Python bindings by adding a caching mechanism for Python method lookups:
Key Changes:
1. New caching infrastructure (in Binding_Controller.h):
- Added member variables to cache:
- m_pySelf - cached Python self reference (avoids repeated py::cast(this))
- m_methodCache - unordered_map storing Python method objects by name
- m_onEventMethod - cached fallback "onEvent" method
- m_hasOnEvent / m_cacheInitialized - state flags
2. New methods (in Binding_Controller.cpp):
- initializePythonCache() - initializes the cache on first use
- getCachedMethod() - retrieves methods from cache (or looks them up once and caches)
- callCachedMethod() - calls a cached Python method with an event
- Constructor and destructor to properly manage the cached Python objects with GIL
3. Optimized handleEvent():
- Previously: every event caused py::cast(this), py::hasattr(), and attr() lookups
- Now: uses cached method references, avoiding repeated Python attribute lookups
4. Optimized getClassName():
- Uses the cached m_pySelf when available instead of casting each time
Purpose:
This is a performance optimization that reduces overhead when handling frequent events (like AnimateBeginEvent, AnimateEndEvent), which can be called many times per simulation step. The caching eliminates repeated Python/C++ boundary crossings for method lookups.
0cb5d02 to
486c35c
Compare
applied @damienmarchal suggestions (particularly the simplification) |
I did detect this issue some time ago, about having a controller in a scene was slowing down the simulation so much, especially on macOS. Even if the controller do nothing. And it gets slower and slower more there are Controllers.
DISCLAIMER: this was mostly the work of Claude, which detected/suggested the issues (lookups were slowing down the simulation) and generated the solution.
I just did the benches/tests to make sure it works, but as for everything with sofapython3 and/or pybind11, I cannot prove everything is okay/well-done. So deep review from experts would be appreciated 🫠
In any case, the modifications lead to a dramatic speed up:
(refer to the scene with this PR, which creates an empy scene with a certain number of Controller doing nothing)
Ubuntu 22.04 (gcc12, i7 13700k)
--> with 10controllers, 150x faster... 😮
Windows (MSVC2026, i7 11800h)
--> with 10controllers, 200x faster... 😲
macOS (xcode26, M3 max)
--> with 10controllers, 837x faster... 🤪