-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improving conversion performance by avoiding repeated imports #9
Comments
that sounds like a good plan to me 👍 This is a follow-up we mentioned but didnt get around implementing: ros2/rclpy#36 (comment) One blocker for implementing that solution at the time was that Windows was unable to link .pyd library to another .pyd lib so we had to go through capsules (that was not an issue on Macos or Linux as they are just reguler shared libraries). |
Ok, I'll check how his is possible on Windows with dll files. Technically dll files supports delay loaded dll files if built by Visual Studio compiler (not so sure about gcc on Windows). That will make import resolving only at first time function is called, not when dll is loaded. This is one option to solve this. I was also thinking of having imports just once - to store pointers globally. This is actually what I've already tried and it worked fine for us. Not sure if this is good solution. I can see this failing if some module/dll file is unloaded, then recompiled and new module/dll loaded with different functions now. Do you expect this kind of situation? |
That would be great! We are targetting MSVC on windows at the moment so if it works with MSVC and not gcc on Windows it's not a blocker for getting it merged.
I thinks that now that type support libraries are loaded at runtime this can work 👍 this was not the case before so we needed to delay the loading to figure out which library should be loaded first.
Not currently especially as all the loaded libraries are message packages I think it's fine not to support this use case |
Ok, I've tried delay loaded dll files on Windows, and they work fine for generated Python conversion code in pyd modules. I'll do a bit more testing, implement Linux part and will submit pull request. There are no issues with dll vs pyd. pyd file is normal dll file. There is nothing special about it. Python simply expects pyd files to export specific symbol when it is loaded for import to work (PyInit_$ModuleName). |
Great news, thanks for looking into it ! |
I have one issue on Linux - they way how shared libraries for python modules are placed in folders, they are not in the same place. For example - std_msgs are built in This would mean I need to update LD_LIBRARY_PATH with location to each folder which contains python module. In this example it would be I'm not sure where can I update it. It seems LD_LIBRARY_PATH is already being changed from You can see my current progress in this commit: https://github.com/martins-mozeiko/rosidl_python/commit/954ecafe3df64c8acf12c7977b8e524f0a896bd9 |
Currently I took pythonpath.py as example and created similar code that creates For example, for std_msgs it looks like this:
Where you would prefer this change?
|
My feeling is that this should work regardless of the build tool used for building the workspace. As adding a python install directory to the library_path is something specific to some packages. I think we should provide a cmake utility to do so, so that it doesn't get appended unconditionally. @dirk-thomas may have more feedback as he is more familiar with this part of the code |
I don't think that adding a per-package library directory is going to work out. On Windows the length of that environment variable would easily exceed the maximum length with a large number of message packages. |
This is not needed on Windows. Only on Linux/macOS. On Windows /delayload takes care of resolving symbols at runtime, not dll load-time. My modifications in 954ecaf already work correctly on Windows without need to change anything else. Only changes to LD_LIBRARY_PATH are needed on Linux (and similar on macOS). |
The CMake can call ament_environment_hooks to add arbitrary hooks. The template can be provided by the package generating the library (I guess I would still try to place the libraries if anyhow possible into a common "LD_LIBRARY_PATH" location before adding these package specific paths to the env var. |
Ok, I'll check how ament_environment_hooks works. I cannot change location of current libraries, because Python requires them to be in specific folders for imports to work. Unless we set PYTHONPATH to lib folder and I'm not sure if this is good approach. I could split library into two parts - so each message package would generate two libraries. One in regular lib folder with convert_from/to_py functions code, but still linking to python .so file for Python API. This way they will be able to link to each other without adjusting ld library path. Then second library (what currently already exists) will link to this new library for calling conversion functions. If introducing new library is OK, then I can also look into this option. |
Here is alternative with splitting generated message library into two libraries: For example, for std_msgs it creates This approach works on Windows and Linux without need to adjust any environment variables. |
On a first look it looks good to me. Please create a PR for it. Also include steps to compare the performance in the PR description so that others can run those to evaluate the difference.
The patch might need some updates in order to work on macOS - just assuming - haven't tried building it yet. |
* Call conversion functions directly This makes generated convert to and from function to call C functions direclty instead of going through Python capsule API. Conversion functions are placed in separate libraries that link to each other. Python module libraries links to these new libraries. See #9 for more details. * generate single _python lib per package * use visibility macros from visibility_control.h * use dylib extension for libraries on macOS * spurious line change * remove duplicated include * blank line fixups * revert unnecessary code relocation * rename variables and use lib folder instad of Lib on macos * create lowercase_field_type for readability and reuse * remove debug prints * rename functions to use double underscore between package name and field type
fixed by #10 |
Feature request
Improve conversion performance for python message objects
Feature description
Every time python message is serialized or unserialized then generated code walks over all message fields and tries to import type which provides converter function pointers. Here and here.
These imports are local - meaning that once function exits, the references are decremented and next time it is called same imports happen again. Doing this for large array of elements or large amount of messages makes serialization/unserialization code pretty slow - especially on weaker platforms like raspberry pi. I have profiled Python code and I can see Python import functionality at top of the profile - they are called hundreds and thousands of times for my use case.
I suggest to call target C functions directly without going through Python. This would require each generated module to export
@(spec.base_type.pkg_name)_@(module_name)__convert_from_py
and@(spec.base_type.pkg_name)_@(module_name)__convert_to_py
functions from shared library.Is this change reasonable?
Implementation considerations
Generator would need to change following places:
create corresponding header file which can be included and target function called directly
build system will to be adjusted to correctly link dependent shared libraries together
Then for all non-primitive fields change generated code to:
include header file that declares conversion function
call C function directly instead of getting function pointer through Python capsule API
Example
Lets look how header field for
sensor_msgs/CompressedImage
message is converted. Currently it has following code in sensor_msgs_compressed_image__convert_from_py function:I propose to generate following code instead:
This would require to add something like
#include "std_msgs/msg/python_header.h"
at beginning of file. And this would happen for all non-primitive fields.std_msgs_header__convert_from_py would be function exported by std_msgs_s__rosidl_typesupport_c.cpython-35m-x86_64-linux-gnu.so file.
The text was updated successfully, but these errors were encountered: