Repository navigation
Convert static types to heap types: use PyType_FromSpec() #84258
Description
Activity
Some of modules is not using PyType_FromSpec.
We need to convert them.This changes can bring
- allow to destroy types at exit!
- allow subinterpreters to have their own "isolated" typ
We need to convert them.
Let me elaborate. Static types have multiple issues:
- Their lifetime is not well defined.
- It is not obvious when they are ready to be used.
- They are not destroyed at exit.
- They are incompatible with subinterpreters: each interpreter should have its own copy of a type, rather than static types are shared by all interpreters which cause problems with reference counting (require GIL or atomic operation).
- They are causing issues with stable ABI (limited C API): PEP-384.
Wouldn't having less static types slow down startup time?
Pablo:
Wouldn't having less static types slow down startup time?
That's possible, even if I only expect a minor or non significant overhead.
But before starting talking about performances, we should focus on the correctness. Static types are causing issues with subinterpreters and the Python finalization.
Wouldn't having less static types slow down startup time?
Yes, and not only startup time:
In the _json module, PyModule_GetState() (called by get_json_state()) is only used by the garbage collector (traverse function) and to unload the module (clear and free functions). It's not used in "hot code" (let me consider that the GC is not part of the usual "hot code" :-)).
But maybe we should measure the overhead of future changes if PyModule_GetState() starts to be used in "hot code" by running a few microbenchmarks.
--
Stefan Krah:
Yes, and not only startup time:
https://bugs.python.org/issue15722Aha, that's interesting. I didn't know that it could have an impact on runtime performance as well.
_decimal_pep3121-384_v1.patch attached to bpo-15722 seems to use:
#define _decimal_state_global ((_decimal_state *)PyModule_GetState(PyState_FindModule(&_decimal_module)))
whereas the commit 33f15a1 only uses:
static inline _jsonmodulestate* get_json_state(PyObject *module) { void *state = PyModule_GetState(module); assert(state != NULL); return (_jsonmodulestate *)state; }
Maybe PyState_FindModule() adds a little overhead, even if this function is simple: in short, it gets the i-th item of a list (from PyInterpreterState.modules_by_index).
PyModule_GetState() function is really simple: it only reads PyModuleObject.md_state attribute.
Or maybe _decimal_state_global was used in "hot code".
If PyState_FindModule() or PyModule_GetState() is the source of the overhead, maybe we could try to optimise these functions, or pass directly the module state to inner functions.
--
PyState_FindModule() doesn't work for a module using PyModuleDef_Init(). The PEP-573 is going to give access to the module state in functions which didn't access to it previsouly.
The commit 33f15a1 removed a few assertions checking that "self" has the expected type. It wasn't possible to get the module state to get the types, because PEP-573 is not implemented yet and PyState_FindModule() doesn't work in _json (which uses PyModuleDef_Init()). I decided that it's ok to remove these assertions: it should not be possible to call these functions with another type in practice.
--
In his PR 19177, Dong-hee started by replacing direct access to PyTypeObject fields, like replacing:
type->free(self);with:
freefunc free_func = PyType_GetSlot(tp, Py_tp_free); free_func(self);I asked him to revert these changes.
I'm interested to experiment a few C extension modules of the stdlib using the limited C API (PEP-384, stable ABI), but I don't think that it should done while converting modules to PyType_FromSpec().
We should separate the two changes. And I would prefer to first see the overhead of PyType_FromSpec(), and discuss the advantages and drawbacks.
Or maybe _decimal_state_global was used in "hot code".
Yes, _decimal has problems here that most modules don't have.
Modules like atexit are obviously fine. :)I just posted it as an example why one should be a bit cautious.
The PEP-573 is going to give access to the module state in functions which didn't access to it previously.
That could help, we'll see.
And I would prefer to first see the overhead of PyType_FromSpec(), and discuss the advantages and drawbacks.
Should we stop the work until the overhead is measured?
Should we stop the work until the overhead is measured?
Can you try to measure the _abc._abc_instancecheck() and _abc._abc_subclasscheck() functions performance before/after your change? Functions like register() are rarely call, so I don't care much of their performance (I expect a small overhead or no overhead).
It shown 1% slower for performance.
+--------------------------+-----------------+----------------------------+
| Benchmark | master-subclass | bpo-40077-subclass |
+==========================+=================+============================+
| bench _abc_subclasscheck | 295 ns | 300 ns: 1.01x slower (+1%) |
+--------------------------+-----------------+----------------------------++--------------------------+-------------------+----------------------------+
| Benchmark | master-isinstance | bpo-40077-isinstance |
+==========================+===================+============================+
| bench _abc_instancecheck | 229 ns | 232 ns: 1.01x slower (+1%) |
+--------------------------+-------------------+----------------------------+49 remaining items
See also bpo-46417 "[subinterpreters] Clear static types in Py_Finalize()".
Converting further static types to heap types require a discussion. See what the Python Steering Council wrote at Feb 8, 2021:
"The Steering Council discussed the ongoing work on porting types in the standard library to heap-types and the subinterpreter-related changes. It was decided that through Pablo, the Steering Council will ask the core developers driving those changes to create an informational PEP and not to make any more changes in this area after beta 1, as per our general policy."
I used the following shell command to search remaining static types:
---
grep -E 'static PyTypeObject [a-zA-Z_0-9]+ *(;|= *{|) *$' $(find -name "*.c")
---I found 86 static types in 17 files:
-
- msidb_Type
- msiview_Type
- record_Type
- summary_Type
-
- DictRemover_Type
- PyComError_Type
- PyDec_Type
- Simple_Type
- StructParam_Type
- Struct_Type
- UnionType_Type
- Union_Type
-
- PyDecContextManager_Type
- PyDecContext_Type
- PyDecSignalDict_Type
-
- Null_Type
- Str_Type
- Xxo_Type
-
- NDArray_Type
- StaticArray_Type
-
Modules/itertoolsmodule.c (multiphase init):
- _grouper_type
- accumulate_type
- combinations_type
- compress_type
- count_type
- cwr_type
- cycle_type
- dropwhile_type
- filterfalse_type
- groupby_type
- pairwise_type
- permutations_type
- starmap_type
- takewhile_type
- tee_type
- teedataobject_type
-
Modules/_xxsubinterpretersmodule.c:
- ChannelIDtype
-
- PyDateTime_DateTimeType
- PyDateTime_DateType
- PyDateTime_DeltaType
- PyDateTime_IsoCalendarDateType
- PyDateTime_TZInfoType
- PyDateTime_TimeType
- PyDateTime_TimeZoneType
-
- ContainerNoGC_type
- GenericAlias_Type
- Generic_Type
- MethClass_Type
- MethInstance_Type
- MethStatic_Type
- MethodDescriptor2_Type
- MethodDescriptorBase_Type
- MethodDescriptorDerived_Type
- MethodDescriptorNopGet_Type
- MyList_Type
- PyRecursingInfinitelyError_Type
- _HashInheritanceTester_Type
- awaitType
- ipowType
- matmulType
- test_structmembersType
-
- PyZoneInfo_ZoneInfoType
-
- OSSAudioType
- OSSMixerType
-
- sock_type
-
- spamdict_type
- spamlist_type
-
- defdict_type
- deque_type
- dequeiter_type
- dequereviter_type
- tuplegetter_type
-
- ElementIter_Type
- Element_Type
- TreeBuilder_Type
- XMLParser_Type
-
- Pdata_Type
- PicklerMemoProxyType
- Pickler_Type
- UnpicklerMemoProxyType
- Unpickler_Type
-
- FutureIterType
- FutureType
- PyRunningLoopHolder_Type
- TaskStepMethWrapper_Type
- TaskType
-
And I found 135 more static types with this command:
---
grep -E '^PyTypeObject [a-zA-Z_0-9]+ *(;|= *{|) *$' $(find -name "*.c")
---Types:
Objects/cellobject.c: PyCell_Type
Objects/sliceobject.c: PyEllipsis_Type
Objects/sliceobject.c: PySlice_Type
Objects/unionobject.c: _PyUnion_Type
Objects/methodobject.c: PyCFunction_Type
Objects/methodobject.c: PyCMethod_Type
Objects/picklebufobject.c: PyPickleBuffer_Type
Objects/boolobject.c: PyBool_Type
Objects/bytearrayobject.c: PyByteArray_Type
Objects/bytearrayobject.c: PyByteArrayIter_Type
Objects/classobject.c: PyMethod_Type
Objects/classobject.c: PyInstanceMethod_Type
Objects/fileobject.c: PyStdPrinter_Type
Objects/genericaliasobject.c: Py_GenericAliasType
Objects/interpreteridobject.c: _PyInterpreterID_Type
Objects/iterobject.c: PySeqIter_Type
Objects/iterobject.c: PyCallIter_Type
Objects/iterobject.c: _PyAnextAwaitable_Type
Objects/moduleobject.c: PyModuleDef_Type
Objects/moduleobject.c: PyModule_Type
Objects/rangeobject.c: PyRange_Type
Objects/rangeobject.c: PyRangeIter_Type
Objects/rangeobject.c: PyLongRangeIter_Type
Objects/namespaceobject.c: _PyNamespace_Type
Objects/bytesobject.c: PyBytes_Type
Objects/bytesobject.c: PyBytesIter_Type
Objects/capsule.c: PyCapsule_Type
Objects/complexobject.c: PyComplex_Type
Objects/dictobject.c: PyDict_Type
Objects/dictobject.c: PyDictIterKey_Type
Objects/dictobject.c: PyDictIterValue_Type
Objects/dictobject.c: PyDictIterItem_Type
Objects/dictobject.c: PyDictRevIterKey_Type
Objects/dictobject.c: PyDictRevIterItem_Type
Objects/dictobject.c: PyDictRevIterValue_Type
Objects/dictobject.c: PyDictKeys_Type
Objects/dictobject.c: PyDictItems_Type
Objects/dictobject.c: PyDictValues_Type
Objects/floatobject.c: PyFloat_Type
Objects/frameobject.c: PyFrame_Type
Objects/funcobject.c: PyFunction_Type
Objects/funcobject.c: PyClassMethod_Type
Objects/funcobject.c: PyStaticMethod_Type
Objects/memoryobject.c: _PyManagedBuffer_Type
Objects/memoryobject.c: PyMemoryView_Type
Objects/odictobject.c: PyODict_Type
Objects/odictobject.c: PyODictIter_Type
Objects/odictobject.c: PyODictKeys_Type
Objects/odictobject.c: PyODictItems_Type
Objects/odictobject.c: PyODictValues_Type
Objects/setobject.c: PySetIter_Type
Objects/setobject.c: PySet_Type
Objects/setobject.c: PyFrozenSet_Type
Objects/setobject.c: _PySetDummy_Type
Objects/tupleobject.c: PyTuple_Type
Objects/tupleobject.c: PyTupleIter_Type
Objects/object.c: _PyNone_Type
Objects/object.c: _PyNotImplemented_Type
Objects/unicodeobject.c: PyUnicode_Type
Objects/unicodeobject.c: PyUnicodeIter_Type
Objects/listobject.c: PyList_Type
Objects/listobject.c: PyListIter_Type
Objects/listobject.c: PyListRevIter_Type
Objects/genobject.c: PyGen_Type
Objects/genobject.c: PyCoro_Type
Objects/genobject.c: _PyCoroWrapper_Type
Objects/genobject.c: PyAsyncGen_Type
Objects/genobject.c: _PyAsyncGenASend_Type
Objects/genobject.c: _PyAsyncGenWrappedValue_Type
Objects/genobject.c: _PyAsyncGenAThrow_Type
Objects/longobject.c: PyLong_Type
Objects/descrobject.c: PyMethodDescr_Type
Objects/descrobject.c: PyClassMethodDescr_Type
Objects/descrobject.c: PyMemberDescr_Type
Objects/descrobject.c: PyGetSetDescr_Type
Objects/descrobject.c: PyWrapperDescr_Type
Objects/descrobject.c: _PyMethodWrapper_Type
Objects/descrobject.c: PyDictProxy_Type
Objects/descrobject.c: PyProperty_Type
Objects/enumobject.c: PyEnum_Type
Objects/enumobject.c: PyReversed_Type
Objects/codeobject.c: _LineIterator
Objects/codeobject.c: _PositionsIterator
Objects/codeobject.c: PyCode_Type
Objects/typeobject.c: PyType_Type
Objects/typeobject.c: PyBaseObject_Type
Objects/typeobject.c: PySuper_Type
Python/bltinmodule.c: PyFilter_Type
Python/bltinmodule.c: PyMap_Type
Python/bltinmodule.c: PyZip_Type
Python/context.c: PyContext_Type
Python/context.c: PyContextVar_Type
Python/context.c: PyContextToken_Type
Python/context.c: _PyContextTokenMissing_Type
Python/hamt.c: _PyHamtItems_Type
Python/hamt.c: _PyHamtKeys_Type
Python/hamt.c: _PyHamtValues_Type
Python/hamt.c: _PyHamt_Type
Python/hamt.c: _PyHamt_ArrayNode_Type
Python/hamt.c: _PyHamt_BitmapNode_Type
Python/hamt.c: _PyHamt_CollisionNode_Type
Python/traceback.c: PyTraceBack_Type
Python/symtable.c: PySTEntry_Type
Modules/_ctypes/callproc.c: PyCArg_Type
Modules/_ctypes/_ctypes.c: PyCStructType_Type
Modules/_ctypes/_ctypes.c: PyCPointerType_Type
Modules/_ctypes/_ctypes.c: PyCArrayType_Type
Modules/_ctypes/_ctypes.c: PyCSimpleType_Type
Modules/_ctypes/_ctypes.c: PyCFuncPtrType_Type
Modules/_ctypes/_ctypes.c: PyCData_Type
Modules/_ctypes/_ctypes.c: PyCFuncPtr_Type
Modules/_ctypes/_ctypes.c: PyCArray_Type
Modules/_ctypes/_ctypes.c: PyCPointer_Type
Modules/_ctypes/cfield.c: PyCField_Type
Modules/_ctypes/stgdict.c: PyCStgDict_Type
Modules/_ctypes/callbacks.c: PyCThunk_Type
Modules/_io/iobase.c: PyIOBase_Type
Modules/_io/iobase.c: PyRawIOBase_Type
Modules/_io/stringio.c: PyStringIO_Type
Modules/_io/textio.c: PyTextIOBase_Type
Modules/_io/textio.c: PyIncrementalNewlineDecoder_Type
Modules/_io/textio.c: PyTextIOWrapper_Type
Modules/_io/bytesio.c: PyBytesIO_Type
Modules/_io/fileio.c: PyFileIO_Type;
Modules/_io/fileio.c: PyFileIO_Type
Modules/_io/winconsoleio.c: PyWindowsConsoleIO_Type;
Modules/_io/winconsoleio.c: PyWindowsConsoleIO_Type
Modules/_io/bufferedio.c: PyBufferedIOBase_Type
Modules/_io/bufferedio.c: PyBufferedReader_Type
Modules/_io/bufferedio.c: PyBufferedWriter_Type
Modules/_io/bufferedio.c: PyBufferedRWPair_Type
Modules/_io/bufferedio.c: PyBufferedRandom_Type
Modules/_multiprocessing/semaphore.c: _PyMp_SemLockType
Modules/_cursesmodule.c: PyCursesWindow_Type;
Modules/_cursesmodule.c: PyCursesWindow_TypeAnd I found 135 more static types with this command
Of these 135 static types, most are cleared since bpo-46417 was implemented:
- 103 types are cleared by _PyTypes_FiniTypes()
- 15 types are cleared by _PyIO_Fini()
- the remaining 17 types are not cleared at Python exit: types of _ctypes, _curses and _multiprocessing extensions
I marked bpo-23769 "valgrind reports leaks for test_zipimport" as duplicate of this issue. At exit, Python doesn't clear the static types of the _collections, itertools and _struct extensions:
- itertools.accumulate
- itertools.combinations
- itertools.combinations_with_replacement
- itertools.cycle
- itertools.dropwhile
- itertools.takewhile
- itertools.islice
- itertools.starmap
- itertools.chain
- itertools.compress
- itertools.filterfalse
- itertools.count
- itertools.zip_longest
- itertools.pairwise
- itertools.permutations
- itertools.product
- itertools.repeat
- itertools.groupby
- itertools._grouper
- itertools._tee
- itertools._tee_dataobject
- collections.deque
- _collections._deque_iterator
- _collections._deque_reverse_iterator
- _collections._tuplegetter
- _struct.Struct
- _struct.unpack_iterator
On Windows PC/winreg.c has PyHKEY_Type static type which isn't cleared at exit too.
PEP 687 was accepted
- added3.12only security fixesonly security fixesand removed3.10 (EOL)end of lifeend of life
on Jun 27, 2022 PEP 687 was accepted
Note that this does not imply that all types shall be converted to heap types. This is also mentioned in the PEP; please read it carefully.
Note that this does not imply that all types shall be converted to heap types. This is also mentioned in the PEP; please read it carefully.
We are on the same page, every module needs to considered individually and I am against converting any built-in modules or types unless it's necessary.
Superseded by #103092
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields: