You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We should provide a more efficient way to get to the module state from the module def.
AFAICT, the function to get the module state is a pure function of the interpreter and the module def.
Since the module def already has a m_index field that is described as "The module's index into its interpreter's modules_by_index cache.", then a Py_GetModuleFromDef() function could be as simple as:
Another thing that could improve performance is to attach the module state to the end of the module object, and save a pointer hop when accessing data in the module state.
Unfortunately m_index is used for PyState_AddModule/PyState_FindModule which
only work on single-phase init modules
assume an interpreter has up to one module object for each PyModuleDef, which is not necessarily true even for single-phase init. In practice PyState_FindModule & m_index work if you don't do anything too wonky, but it's far from sound.
Avoiding the pointer hop would make it infeasible to revive PEP-547, which assumes you can add state to an existing (but fresh) __main__ module. Perhaps it's worth the performance improvement; I don't think either of us would be a good judge for that.
Can you create several modules from the same PyModuleDef? That sounds like a bug to me, if you can.
I don't think there is any fundamental reason why m_index needs to be restricted to single-phase init modules. That's just how it currently works, AFAICT.
We could always add another index field, if we can't change it.
Avoiding the pointer hop would make it infeasible to revive PEP-547,
It would prevent the mechanism proposed in PEP 547, but we could achieve the goal of PEP 547 with a different mechanism.
But we can leave that for now, and speed up finding the module first.
Can you create several modules from the same PyModuleDef? That sounds like a bug to me, if you can.
You can find an example in Modules/_testsinglephase.c.
Another case: loading the same module more than once (which isn't the same thing as importing multiple times or reloading). Each time an extension module is directly loaded, a new module is created.
(Naturally, that isn't strictly true for all modules. Single-phase init modules with m_size < 0 are only ever loaded once. Also, reloading a single-phase init module with m_size >-0 does actually load it from scratch, creating a new module.)
That test was added recently, and seems to be testing a weird corner case that I suspect no one uses.
For the vast majority of modules that want the sane behavior of one module per module def and want reasonable performance, we should provide that.
So, for backwards compatibility, we will need to leave PyType_GetModuleByDef as it is.
That doesn't prevent us from adding Py_GetModuleFromDef() for those modules that don't need or want to support reloading.
If we add a module option that ensures the module can't be initialized more than once, it would make sense to disable finalization as well (or, delay it until interpreter shutdown). That would get us immortal singleton modules (per-interpreter), which does sound like a reasonable feature.
@mdboom If this issue is about micro-benchmarking, I would like to see the result of the main vs that with the diff (a) below applied, on the pi_cdecimal test in Modules/_decimal/tests/bench.py with additional iterations.
As to the macro-benchmarks, I'm not sure how impactful the PyType_GetModuleByDef is. The same goes for the _decimal pi test case, once class D(C.Decimal): pass is used instead of D = C.Decimal.
Two PyObject_GC_Track() have been added to _decimal.c, where the telco benchmarks on my Windows PGO builds have shown insignificant slowdowns by 0%-2%. However, I can no longer see actual performance improvements by using PyInterpreterState_Get() there instead.
Why not modifying _decimalPyDecContextObject to store directly the module state? I would avoid have go through ctx => type => module => module state, knowning that getting the module from a type is "slow".
Activity
markshannon commentedon May 28, 2024
@ericsnowcurrently
@encukou
markshannon commentedon May 29, 2024
Another thing that could improve performance is to attach the module state to the end of the module object, and save a pointer hop when accessing data in the module state.
encukou commentedon May 29, 2024
Unfortunately
m_indexis used forPyState_AddModule/PyState_FindModulewhichPyModuleDef, which is not necessarily true even for single-phase init. In practicePyState_FindModule&m_indexwork if you don't do anything too wonky, but it's far from sound.Avoiding the pointer hop would make it infeasible to revive PEP-547, which assumes you can add state to an existing (but fresh)
__main__module. Perhaps it's worth the performance improvement; I don't think either of us would be a good judge for that.markshannon commentedon May 29, 2024
Can you create several modules from the same
PyModuleDef? That sounds like a bug to me, if you can.I don't think there is any fundamental reason why
m_indexneeds to be restricted to single-phase init modules. That's just how it currently works, AFAICT.We could always add another index field, if we can't change it.
It would prevent the mechanism proposed in PEP 547, but we could achieve the goal of PEP 547 with a different mechanism.
But we can leave that for now, and speed up finding the module first.
ericsnowcurrently commentedon May 29, 2024
You can find an example in Modules/_testsinglephase.c.
Another case: loading the same module more than once (which isn't the same thing as importing multiple times or reloading). Each time an extension module is directly loaded, a new module is created.
(Naturally, that isn't strictly true for all modules. Single-phase init modules with
m_size < 0are only ever loaded once. Also, reloading a single-phase init module withm_size >-0does actually load it from scratch, creating a new module.)markshannon commentedon May 30, 2024
That test was added recently, and seems to be testing a weird corner case that I suspect no one uses.
For the vast majority of modules that want the sane behavior of one module per module def and want reasonable performance, we should provide that.
So, for backwards compatibility, we will need to leave
PyType_GetModuleByDefas it is.That doesn't prevent us from adding
Py_GetModuleFromDef()for those modules that don't need or want to support reloading.encukou commentedon May 31, 2024
It's a new test, the behaviour is old.
If we add a module option that ensures the module can't be initialized more than once, it would make sense to disable finalization as well (or, delay it until interpreter shutdown). That would get us immortal singleton modules (per-interpreter), which does sound like a reasonable feature.
neonene commentedon Jun 5, 2024
@mdboom If this issue is about micro-benchmarking, I would like to see the result of the main vs that with the diff (a) below applied, on the
pi_cdecimaltest inModules/_decimal/tests/bench.pywith additional iterations.As to the macro-benchmarks, I'm not sure how impactful the
PyType_GetModuleByDefis. The same goes for the_decimalpi test case, onceclass D(C.Decimal): passis used instead ofD = C.Decimal.(a) interpreter state access
(b) global state access (attached just in case)
neonene commentedon Aug 26, 2024
Two
PyObject_GC_Track()have been added to_decimal.c, where thetelcobenchmarks on my Windows PGO builds have shown insignificant slowdowns by 0%-2%. However, I can no longer see actual performance improvements by usingPyInterpreterState_Get()there instead.vstinner commentedon Aug 26, 2024
Why not modifying
_decimalPyDecContextObjectto store directly the module state? I would avoid have go through ctx => type => module => module state, knowning that getting the module from a type is "slow".