Details
-
Task
-
Resolution: Incomplete
-
P2: Important
-
None
-
None
-
None
-
c0b74a794 (dev), 8aa92c970 (6.5), 2bb8b0f7b (dev), a6b47fbd8 (6.6), 6e897a9ef (dev), 7f69d4d56 (dev), d9f3fb812 (dev), ceae763ac (6.6), 9b2ba5e6b (dev), d7b1c851d (6.6), 9b240cd08 (dev), 14618acc1 (6.6), dcbe4810a (dev), 546548acc (dev), fb0270f39 (dev)
Description
The Initialization Problem
All PySide types are created at the very beginning of a program, when importing a module. This costs a little bit of non-negligible time, and by introducing the new enums this effect becomes even more obvious:
- New enums require a considerable amount of Python code, which is naturally about 20-30 times slower than the C++ equivalent.
- If you look at the effort that goes into initialization compared to the benefit, you see that most of it could be saved by not initializing at all.
Early attempts
A first, relatively naive approach was not to create enums directly, but only when they are used. For this purpose initialization checks were built into functions like PyObject_GetAttr. This worked quite well, but was not reliable:
- Besides the mentioned changes there are many other places where initializations have to be checked.
- This also happens in all sorts of conversion functions that show up everywhere in the generated wrapper code.
- All these conversion functions must still work, even if the used Python wrappers don't exist yet.
How to solve the problem in the first place?
After various other attempts, this approach emerged as promising in the end:
Register classes that do not exist yet
In the original implementation there is a mapping in every module which assigns class names to wrapper classes. This is the communication center of every module. It looks like an unsolvable problem to stop creating this structure. But there is another possibility:
The essential new approach is now to change this mapping in such a way that the classes are still found, but without the classes necessarily having to exist.
- Instead of registering classes, we register functions that can create such classes.
- The access to the classes, e.g. in conversion functions, is embedded into a Shiboken::resolve function, so that for the view of the calling function nothing changes: The requested class is delivered, even if this is initialized only by the resolve function.
The existing mapping is changed to both support the PyObjectType directly as before or to call a PyObjectType-valued function which then generates the PyTypeObject just in time. This way, by supporting both possibilities, the change to late initialization can be done gradually. We can call that Dual Registration.
Concrete Example of Dual Registration Preparation
Normally, the QtCore module header contains these structures for registration:
// Current module's type array. PyTypeObject **SbkPySide6_QtCoreTypes = nullptr; // Current module's PyObject pointer. PyObject *SbkPySide6_QtCoreModuleObject = nullptr; // Current module's converter array. SbkConverter **SbkPySide6_QtCoreTypeConverters = nullptr;
and a usage example is
Shiboken::Conversions::pointerToPython(SbkPySide6_QtCoreTypes[SBK_QCHILDEVENT_IDX], event)
New PyTypeTypeF * array replaces PyTypeObject **
The preparational new structure looks like this, instead:
// Current module's type array. Shiboken::PyTypeTypeF *SbkPySide6_QtCoreTypes = nullptr; // Current module's PyObject pointer. PyObject *SbkPySide6_QtCoreModuleObject = nullptr; // Current module's converter array. SbkConverter **SbkPySide6_QtCoreTypeConverters = nullptr;
and its slightly modified usage like this:
Shiboken::Conversions::pointerToPython(Shiboken::resolve(SbkPySide6_QtCoreTypes[SBK_QCHILDEVENT_IDX]), event)
Instead of an array of PyTypeObject *, the added Shiboken::resolve function now operates on these pairs:
struct PyTypeTypeF { PyTypeObject *pyType; PyTypeObject *(*pyTypeF)(); };
This function is very simple, but with a huge effect:
SbkObjectType *resolve(const PyTypeTypeF &pair) { return pair.pyTypeF ? pair.pyTypeF() : pair.pyType; }
- If the pyTypeF field is not set, then the return value is taken from PyTypeObject * directly, as it was before this whole change.
- If the pyTypeF field is set to a PyTypeObject * valued function, this function is called. It has to perform the lazy initialization.
Note the effect:
- The requested type is still delivered at the right time by construction. But now the type creation is done just in the latest moment possible.
- The registration is still well defined because it describes how to get from name to type. The values are constant and will never be changed after assignment.
A New Approach: Delay at the Import Level
Thinking of possible Initialization savings in Python, there is a working example from Python itself:
PEP 690
This PEP defines how modules in Python can be delay-loaded. A delayed module is not eagerly loaded when the import happens, but the real import happens when something from the module is actually used.
In PySide, the situation is slightly different, because it is not enough to trigger execution when the first object of the module is used. This would almost every time trigger. The saving for PySide modules occurs only when this is done finer-grained, i.E. per class. But the idea is quite simirar. It is the implementation of
- Load Classes Only when they are Used
The necessary functions have already been implemented in sbkmodules.cpp. The module import is modified in a way that only the names of classes become defined. They are no Python objects, yet. Only when a getattr function is called, the initialization takes place.
There is an advantage over the former approaches, as we now try not to change lots of generated initialization code and many conversion functions. Instead, we try not to touch anything but simply to initialize class access by the intercepted module_getattr function.
Remaining Problems with New Approach
While the new way simplifies things very much, we still have the problem that PySide modules want to fill a static array of readily generated classes. In the past approach we tried to delay this initialization by using class generating functions instead of the classes themselves. This pushed the complication into the code generator, which made trying of different approaches very tedious and error-prone.
Update 2024-01-17: First tests are very positive!
Update 2024-04-04: This is now finally integrated.
... and needs a fix for polymorphic functions
Attachments
Issue Links
- relates to
-
PYSIDE-2374 Improve PySide startup time
- Open
-
PYSIDE-2720 PySide6 working much slower than PySide2
- Closed
-
PYSIDE-2780 Lazy import reference counting bug
- Closed
- resulted in
-
PYSIDE-2888 REG->6.8.0: crashes after "import PySide6; from PySide6.QtCore import *"
- Closed
For Gerrit Dashboard: PYSIDE-2404 | ||||||
---|---|---|---|---|---|---|
# | Subject | Branch | Project | Status | CR | V |
567296,1 | Lazy Load: Remove exclusion for testbinding | dev | pyside/pyside-setup | Status: NEW | 0 | -1 |
496118,40 | LazyInit: Implement the basis for Lazy Initialization | dev | pyside/pyside-setup | Status: ABANDONED | 0 | 0 |
497316,10 | WIP: shiboken: Implement Lazy Initialization in "Import from" | dev | pyside/pyside-setup | Status: ABANDONED | -2 | 0 |
531542,3 | shiboken: Allow to disable pyi file generation for debugging | 6.6 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
534306,13 | LazyInit: Make imports computable from offset constants | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
535291,5 | shiboken6: Refactor writing out type indices | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
536322,12 | LazyInit: Fix the various error categories | dev | pyside/pyside-setup | Status: ABANDONED | 0 | 0 |
538000,4 | LazyInit: Activate Lazy Loading per default | dev | pyside/pyside-setup | Status: ABANDONED | +1 | 0 |
538879,6 | LazyInit: Improve the handling of QtUiTools and QtXml | dev | pyside/pyside-setup | Status: ABANDONED | 0 | 0 |
539111,5 | shiboken: Fix the polymorphic names in styleOptionType | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
539129,19 | Feature: Prepare feature and signature modules to stand lazy init | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
539130,7 | LazyInit: Implement Lazy Initialization by Delayed Module Entries | dev | pyside/pyside-setup | Status: ABANDONED | 0 | 0 |
539269,2 | shiboken: Fix the polymorphic names in styleOptionType | 6.6 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
539467,35 | LazyInit: Implement Lazy Initialization by Delayed Module Entries | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
539853,17 | Enum: Move special Flag patch into a snippet | dev | pyside/pyside-setup | Status: MERGED | -1 | 0 |
540420,8 | Fix startup delay caused by initializing numpy | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
540422,3 | Add benchmark script for lazy initialization | dev | pyside/pyside-setup | Status: MERGED | -1 | 0 |
540601,2 | Add benchmark script for lazy initialization, comment fix | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
540618,2 | Add benchmark script for lazy initialization | 6.6 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
540867,2 | Add benchmark script for lazy initialization, comment fix | 6.6 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
540975,32 | LazyInit: Move the get arguments into a static structure | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
545557,1 | Enum: Move special Flag patch into a snippet | 6.6 | pyside/pyside-setup | Status: ABANDONED | -2 | 0 |
547065,6 | shiboken tests/otherbinding: Add a test for star imports | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
547210,1 | WIP: Check that disabling Nuitka solves Lazy Init | dev | pyside/pyside-setup | Status: ABANDONED | -2 | 0 |
548269,8 | Lazy Init: Evict the Achilles Heel of Laziness and fix Nuitka | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
548322,2 | shiboken tests/otherbinding: Add a test for star imports, amended | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
550212,2 | Lazy Init: Evict the Achilles Heel of Laziness and fix Nuitka | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
554315,5 | Lazy Import: Ensure type creation functions being idempotent | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
554419,2 | Lazy Import: Ensure type creation functions being idempotent | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
554458,2 | shiboken6: Add a way of disable lazy initialization per class | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
554548,2 | shiboken6: Add a way of disable lazy initialization per class | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
556000,3 | libshiboken: Remove unnecessary std::string usage | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
556039,2 | libshiboken: Remove unnecessary std::string usage | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
557226,4 | libshiboken: Refactor type discovery graph handling | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
557227,4 | Revert "Lazy Load: Fix polymorphic classes by identifying lazy groups" | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
557248,4 | libshiboken: Extract a template base class for the dependency graph | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
557249,8 | shiboken6: Lazy-initialize the dependency graph | dev | pyside/pyside-setup | Status: MERGED | +2 | +1 |
557533,2 | libshiboken: Refactor type discovery graph handling | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
557588,2 | Revert "Lazy Load: Fix polymorphic classes by identifying lazy groups" | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
557589,2 | libshiboken: Extract a template base class for the dependency graph | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
557590,2 | shiboken6: Lazy-initialize the dependency graph | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
559222,4 | numpy initialization: Remove superfluous PyErr_Clear() | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
559319,2 | numpy initialization: Remove superfluous PyErr_Clear() | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
560470,5 | Shiboken: Simplify Python Error Messages | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
560563,2 | Shiboken: Simplify Python Error Messages | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
560690,9 | Lazy Init: Support Lazy Subtypes | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
562024,7 | LazyInit: Speed up QObject conversion | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
562059,10 | LazyInit: Optimize access to non-existing types by caching | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
562362,3 | libpyside: Remove duplicated LoadLazyClassesWithName() call | dev | pyside/pyside-setup | Status: MERGED | +2 | +1 |
562408,2 | libpyside: Remove duplicated LoadLazyClassesWithName() call | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
562418,2 | LazyInit: Speed up QObject conversion | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
562424,2 | libpyside: Remove duplicated LoadLazyClassesWithName() call, take 2 | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
562447,2 | libpyside: Remove duplicated LoadLazyClassesWithName() call, take 2 | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
562537,3 | LazyInit: Optimize access to non-existing types by caching | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
562714,2 | shibokenmodule: Handle nullptr (non-existent) in dumpConverters() | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
562732,2 | libpyside: Remove duplicated LoadLazyClassesWithName() call | 6.7.1 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
562733,2 | LazyInit: Speed up QObject conversion | 6.7.1 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
562734,2 | libpyside: Remove duplicated LoadLazyClassesWithName() call, take 2 | 6.7.1 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
562735,2 | LazyInit: Optimize access to non-existing types by caching | 6.7.1 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
562810,2 | libpyside: Remove duplicated LoadLazyClassesWithName() call | tqtc/6.7.1 | pyside/tqtc-pyside-setup | Status: MERGED | +2 | 0 |
562811,2 | LazyInit: Speed up QObject conversion | tqtc/6.7.1 | pyside/tqtc-pyside-setup | Status: MERGED | +2 | 0 |
562812,2 | libpyside: Remove duplicated LoadLazyClassesWithName() call, take 2 | tqtc/6.7.1 | pyside/tqtc-pyside-setup | Status: MERGED | +2 | 0 |
562813,2 | LazyInit: Optimize access to non-existing types by caching | tqtc/6.7.1 | pyside/tqtc-pyside-setup | Status: MERGED | +2 | 0 |
563335,2 | Silence warning about deprecated type array in Qt modules | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
563366,2 | Silence warning about deprecated type array in Qt modules | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
563625,4 | shiboken: add missing include | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
563690,2 | shiboken: add missing include | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
563930,2 | shiboken: add missing include | 6.7.1 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
564415,2 | libshiboken: Remove left-over declarations of lazy loading functions | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
564447,1 | libshiboken: Remove left-over declarations of lazy loading functions | 6.7 | pyside/pyside-setup | Status: ABANDONED | +2 | 0 |
564507,8 | WIP: shiboken6: Extract a helper for determining a smart pointer instantiation target lang name | dev | pyside/pyside-setup | Status: DEFERRED | -2 | 0 |
564528,11 | Fix lazy loading of smart pointers | dev | pyside/pyside-setup | Status: MERGED | +2 | +1 |
564594,2 | Lazy Init: Fix crash when setting an error message | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
564619,3 | shiboken6: Add tests for std::shared_ptr<std::string> | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
564648,2 | Lazy Init: Fix crash when setting an error message | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
564658,2 | shiboken6: Add tests for std::shared_ptr<std::string> | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
564800,4 | shiboken6: Add tests for a named smart pointer instance | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
565028,2 | WIP: Fix lazy loading of smart pointers in namespaces (subtypes), approach 1 | dev | pyside/pyside-setup | Status: ABANDONED | -2 | 0 |
565505,2 | shiboken6: Add tests for a named smart pointer instance | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
565549,8 | shiboken6: Fix type names of smart pointers in namespaces in the cppApi array | dev | pyside/pyside-setup | Status: MERGED | +2 | +1 |
565916,2 | shiboken6: Fix type names of smart pointers in namespaces in the cppApi array | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
566466,3 | WIP: Lazy Load: Enable the shiboken6 tests | dev | pyside/pyside-setup | Status: ABANDONED | -2 | -1 |
566519,3 | Lazy Load: Disable lazy for PyPy | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
566573,11 | Lazy Load: Fix smart pointers with converters to smart pointers to pointee base | dev | pyside/pyside-setup | Status: MERGED | +2 | +1 |
566794,4 | shiboken6: Refactor writing the init functions into the module source | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
566805,2 | Lazy Load: Fix crash caused by global enumerations in samplebinding | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
566873,4 | Lazy Load: Fix crash caused by global enumerations in samplebinding | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
566914,3 | Lazy Load: Disable lazy for PyPy | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
567118,3 | Fix non-unity build | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
567141,3 | Fix non-unity build | 6.7 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
576913,23 | shiboken: Compress signature strings with minimal overhead | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
580709,2 | WIP: Fix crash in lazy loading | dev | pyside/pyside-setup | Status: ABANDONED | -2 | 0 |
597328,6 | Lazy Init: Support Lazy Subtypes, amended | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
597489,2 | Lazy Init: Support Lazy Subtypes, amended | 6.8 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
597518,2 | Lazy Init: Support Lazy Subtypes, amended | 6.8.0 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
597542,2 | Lazy Init: Support Lazy Subtypes, amended | tqtc/6.8.0 | pyside/tqtc-pyside-setup | Status: MERGED | +2 | 0 |
598427,8 | Lazy Init: Support Lazy Subtypes in star imports, amendment 2 | dev | pyside/pyside-setup | Status: MERGED | +2 | 0 |
598617,2 | Lazy Init: Support Lazy Subtypes in star imports, amendment 2 | 6.8 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
598708,2 | Lazy Init: Support Lazy Subtypes in star imports, amendment 2 | 6.8.0 | pyside/pyside-setup | Status: MERGED | +2 | 0 |
598785,2 | Lazy Init: Support Lazy Subtypes in star imports, amendment 2 | tqtc/6.8.0 | pyside/tqtc-pyside-setup | Status: MERGED | +2 | 0 |