From 3fe96eba2042cb90c1117cf619919f2099ce1a30 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 25 Feb 2022 17:19:32 -0700 Subject: [PATCH 01/26] Update the post history. --- pep-0683.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0683.rst b/pep-0683.rst index 32f5e455697..4c8fca1de07 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -7,7 +7,7 @@ Type: Standards Track Content-Type: text/x-rst Created: 10-Feb-2022 Python-Version: 3.11 -Post-History: 15-Feb-2022 +Post-History: 15-Feb-2022,19-Feb-2022 Resolution: From 6771a73b146ae151e1bd04dd6cb5fc03951b714b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 25 Feb 2022 17:24:08 -0700 Subject: [PATCH 02/26] Expand the abstract. --- pep-0683.rst | 82 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index 4c8fca1de07..3405613409e 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -19,29 +19,65 @@ Currently the CPython runtime maintains a allocated memory of each object. Because of this, otherwise immutable objects are actually mutable. This can have a large negative impact on CPU and memory performance, especially for approaches to increasing -Python's scalability. The solution proposed here provides a way -to mark an object as one for which that per-object -runtime state should not change. - -Specifically, if an object's refcount matches a very specific value -(defined below) then that object is treated as "immortal". If an object -is immortal then its refcount will never be modified by ``Py_INCREF()``, -etc. Consequently, the refcount will never reach 0, so that object will -never be cleaned up (unless explicitly done, e.g. during runtime -finalization). Additionally, all other per-object runtime state -for an immortal object will be considered immutable. - -This approach has some possible negative impact, which is explained -below, along with mitigations. A critical requirement for this change -is that the performance regression be no more than 2-3%. Anything worse -than performance-neutral requires that the other benefits are proportionally -large. Aside from specific applications, the fundamental improvement -here is that now an object can be truly immutable. - -(This proposal is meant to be CPython-specific and to affect only -internal implementation details. There are some slight exceptions -to that which are explained below. See `Backward Compatibility`_, -`Public Refcount Details`_, and `scope`_.) +Python's scalability. + +This proposal mandates that, internally, CPython will support marking +an object as one for which that runtime state will no longer change. +Consequently, such an object's refcount will never reach 0, and so the +object will never be cleaned up. We call these objects "immortal". +(Normally, only a relatively small number of internal objects +will ever be immortal.) + +The fundamental improvement here is that now an object +can be truly immutable. + +Scope +----- + +Object immortality is meant to be an internal-only feature. +So this proposal does not include any changes to public API or behavior. +However, this does not prevent us from adding (publicly accessible) +private API to do things like immortalize an object or tell if one +is immortal. + +Any effort to expose this feature to users would need to be proposed +separately. There is one exception: refcounting semantics for immortal +objects will differ in some cases from user expectations. This +exception, and the solution, are discussed below. + +Most of this PEP focuses on an internal implementation that satisfies +the above mandate. However, those implementation details are not meant +to be strictly proscriptive. Instead, at the least they are included +to help illustrate the technical considerations required by the mandate. +The actual implementation may deviate somewhat. Furthermore, the +acceptability of any implementation detail described below does not +depend on the status of this PEP, unless explicitly.specified. + +For example, the particular details of: + +* how to mark something as immortal +* how to recognize something as immortal +* which subset of functionally immortal objects are marked as immortal +* which memory-management activities are skipped or modified for immortal objects + +are not only CPython-specific but are also private implementation +details that are expected to change in subsequent versions. + +Implementation Summary +---------------------- + +Here's a high-level look at the implementation: + +If an object's refcount matches a very specific value (defined below) +then that object is treated as immortal. The CPython C-API and runtime +will not modify the refcount (or other runtime state) of an immortal +object. + +Aside from the change to refcounting semantics, there is one other +possible negative impact to consider. A naive implementation of the +approach described below makes CPython roughly 4% slower. However, +the implementation is performance-neutral once known mitigations +are applied. Motivation From dd3861dc7a8854bfd691f20b0dd0663f6e6a7ed2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 25 Feb 2022 17:24:59 -0700 Subject: [PATCH 03/26] Be a little less specific. --- pep-0683.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0683.rst b/pep-0683.rst index 3405613409e..a4078423ed4 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -189,7 +189,7 @@ Impact Benefits -------- -Most notably, the cases described in the two examples above stand +Most notably, the cases described in the above examples stand to benefit greatly from immortal objects. Projects using pre-fork can drop their workarounds. For the per-interpreter GIL project, immortal objects greatly simplifies the solution for existing static From 378503957f82b2cc35aeb29fd2306db65484730c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 25 Feb 2022 17:26:19 -0700 Subject: [PATCH 04/26] Drop the old scope section. --- pep-0683.rst | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index a4078423ed4..f47949a78e7 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -242,7 +242,7 @@ unless it checks for hard-coded small values relative to some immortal object. The problems noticed by `Pyston`_ shouldn't apply here since we do not modify the refcount. -See `Public Refcount Details`_ and `scope`_ below for further discussion. +See `Public Refcount Details`_ below for further discussion. Stable ABI ---------- @@ -380,27 +380,6 @@ Constraints to persist until runtime finalization. * be careful when immortalizing objects that are not otherwise immutable -.. _scope: - -Scope of Changes ----------------- - -Object immortality is not meant to be a public feature but rather an -internal one. So the proposal does *not* include adding any new -public C-API, nor any Python API. However, this does not prevent -us from adding (publicly accessible) private API to do things -like immortalize an object or tell if one is immortal. - -The particular details of: - -* how to mark something as immortal -* how to recognize something as immortal -* which subset of functionally immortal objects are marked as immortal -* which memory-management activities are skipped or modified for immortal objects - -are not only Cpython-specific but are also private implementation -details that are expected to change in subsequent versions. - Immortal Mutable Objects ------------------------ From c84919de35571af334c73a62407a1c92ea3c9868 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 25 Feb 2022 20:01:51 -0700 Subject: [PATCH 05/26] Update the performance numbers. --- pep-0683.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index f47949a78e7..0d64b1da8f9 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -203,10 +203,9 @@ usage. This is reflected in most of the above cases. Performance ----------- -A naive implementation shows `a 4% slowdown`_. -Several promising mitigation strategies will be pursued in the effort -to bring it closer to performance-neutral. See the `mitigation`_ -section below. +A naive implementation shows `a 4% slowdown`_. We have demonstrated +a return to performance-neutral with a handful of basic mitigations +applied. See the `mitigation`_ section below. On the positive side, immortal objects save a significant amount of memory when used with a pre-fork model. Also, immortal objects provide From bf8257e16e583df08cc77717d560212763efaba2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 25 Feb 2022 20:02:24 -0700 Subject: [PATCH 06/26] Elaborate on refcounts and accidental immortality. --- pep-0683.rst | 142 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 31 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index 0d64b1da8f9..2ad1a076abd 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -217,24 +217,31 @@ performance. Backward Compatibility ---------------------- -This proposal is meant to be completely compatible. It focuses strictly -on internal implementation details. It does not involve changes to any -public API, other than a few minor changes in behavior related to refcounts -(but only for immortal objects): +Ideally this internal-only feature would be completely compatible. +However, it does involve a change to refcount semantics in some cases. +Only immortal objects are affected, but this includes high-use objects +like `None`, `True`, and `False`. + +Specifically, when an immortal object is involved: * code that inspects the refcount will see a really, really large value * the new noop behavior may break code that: * depends specifically on the refcount to always increment or decrement (or have a specific value from ``Py_SET_REFCNT()``) - * relies on any specific refcount value, other than 0 + * relies on any specific refcount value, other than 0 or 1 * directly manipulates the refcount to store extra information there +* in 32-bit pre-3.11 `Stable ABI`_ extensions, + objects may leak due to `Accidental Immortality`_ +* such extensions may crash due to `Accidental De-Immortalizing`_ + Again, those changes in behavior only apply to immortal objects, not most of the objects a user will access. Furthermore, users cannot mark an object as immortal so no user-created objects will ever have that changed behavior. Users that rely on any of the changing behavior for -global (builtin) objects are already in trouble. +global (builtin) objects are already in trouble. So the overall impact +should be small. Also note that code which checks for refleaks should keep working fine, unless it checks for hard-coded small values relative to some immortal @@ -243,33 +250,19 @@ we do not modify the refcount. See `Public Refcount Details`_ below for further discussion. -Stable ABI ----------- - -The approach is also compatible with extensions compiled to the stable -ABI. Unfortunately, they will modify the refcount and invalidate all -the performance benefits of immortal objects. However, the high bit -of the refcount `will still match _Py_IMMORTAL_REFCNT <_Py_IMMORTAL_REFCNT_>`_ -so we can still identify such objects as immortal. At worst, objects -in that situation would feel the effects described in the `Motivation`_ -section. Even then the overall impact is unlikely to be significant. - -Also see `_Py_IMMORTAL_REFCNT`_ below. - Accidental Immortality ----------------------- +'''''''''''''''''''''' -Hypothetically, a regular object could be incref'ed so much that it -reaches the magic value needed to be considered immortal. That means -it would accidentally never be cleaned up (by going back to 0). +Hypothetically, a non-immortal object could be incref'ed so much +that it reaches the magic value needed to be considered immortal. +That means it would accidentally never be cleaned up +(by going back to 0). -While it isn't impossible, this accidental scenario is so unlikely -that we need not worry. Even if done deliberately by using -``Py_INCREF()`` in a tight loop and each iteration only took 1 CPU -cycle, it would take 2^61 cycles (on a 64-bit processor). At a fast -5 GHz that would still take nearly 500,000,000 seconds (over 5,000 days)! -If that CPU were 32-bit then it is (technically) more possible though -still highly unlikely. +On 64-bit builds, this accidental scenario is so unlikely that we need +not worry. Even if done deliberately by using ``Py_INCREF()`` in a +tight loop and each iteration only took 1 CPU cycle, it would take +2^60 cycles (on a 64-bit processor). At a fast 5 GHz that would +still take nearly 250,000,000 seconds (over 2,500 days)! Also note that it is doubly unlikely to be a problem because it wouldn't matter until the refcount got back to 0 and the object was cleaned up. @@ -280,9 +273,95 @@ would be noticed. Again, the only realistic way that the magic refcount would be reached (and then reversed) is if it were done deliberately. (Of course, the same thing could be done efficiently using ``Py_SET_REFCNT()`` though -that would be even less of an accident.) At that point we don't +that would be even less of an accident.) At that point we don't consider it a concern of this proposal. +On 32-bit builds it isn't so obvious. The magic refcount would be 2^28. +Using the same specs as above, it would take roughly 1 second to +accidentally immortalize an object. Under reasonable conditions, it +is still highly unlikely that an object be accidentally immortalized. +It would have to meet these criteria: + +* targeting a non-immortal object (so not one of the high-use builtins) +* the extension increfs without a corresponding decref + (e.g. returns from a function or method) +* no other code decrefs the object in the meantime + +Under those conditions it would reach accidental immortality (on 32-bit) +in, at most, a year if it averaged at least one of those increfs every +158 seconds on that hypothetical workstation. Of course, then it would +have to run through the same number of (now noop-ing) decrefs before +that one object would be effectively leaking. This is highly unlikely, +especially because we assume no decrefs. + +Furthermore, this isn't all that different from how such 32-bit extensions +can already incref an object past 2^31 and turn the refcount negative. +If that were an actual problem then we would have heard about it. + +Between all of the above cases, the proposal doesn't consider +accidental immortality a problem. + +Stable ABI +'''''''''' + +The implementation approach described in this PEP is compatible +with extensions compiled to the stable ABI (with the exception +of `Accidental Immortality`_ and `Accidental De-Immortalizing`_). +Due to the nature of the stable ABI, unfortunately, such extensions +use versions of ``Py_INCREF()``, etc. that directly modify the object's +``ob_refcnt`` field. This will invalidate all the performance benefits +of immortal objects. + +However, we do ensure that immortal objects (mostly) stay immortal +in that situation. We set the initial refcount of immortal objects to +a value high above the magic refcount value, but one that still matches +the high bit. Thus we can still identify such objects as immortal. +(See `_Py_IMMORTAL_REFCNT`_.) At worst, objects in that situation +would feel the effects described in the `Motivation`_ section. +Even then the overall impact is unlikely to be significant. + +Accidental De-Immortalizing +''''''''''''''''''''''''''' + +32-bit builds of older stable ABI extensions can take `Accidental Immortality`_ +to the next level. + +Hypothetically, such an extension could incref an object to value on +the next highest bit above the magic refcount value. For example, if +the magic value were 2^30 and the initial immortal refcount were thus +2^30 + 2^29 then it would take 2^29 increfs by the extension to reach +a value of 2^31, making the object non-immortal. +(Of course, a refcount that high would probably already cause a crash, +regardless of immortal objects.) + +The more problematic case is where such a 32-bit stable ABI extension +goes crazy decref'ing an already immortal object. Continuing with the +above example, it would take 2^29 asymmetric decrefs to drop below the +magic immortal refcount value. So an object like ``None`` could be +made mortal and subject to decref. That still wouldn't be a problem +until somehow the decrefs continue on that object until it reaches 0. +For many immortal objects, like ``None``, the extension will crash +the process if it tries to dealloc the object. For the other +immortal objects, the dealloc might be okay. However, there will +be runtime code expecting the formerly-immortal object to be around +forever. That code will probably crash. + +Again, the likelihood of this happening is extremely small, even on +32-bit builds. It would require roughly a billion decrefs on that +one object without a corresponding incref. The most likely scenario is +the following: + +A "new" reference to ``None`` is returned by many functions and methods. +The 3.11 runtime will never incref it before giving it to the extension. +However, the extension *will* decref it when done with it. Each time +that exchange happens with the one object, we get one step closer to a +crash. How realistic is it that some form of that exchange happen +a billion times in the lifetime of a Python process on 32-bit? + + + +.. My gut says it is not realistic but that's not very compelling. + Alternate Python Implementations -------------------------------- @@ -603,6 +682,7 @@ Open Issues * is there any other impact on GC? * `are the copy-on-write benefits real? `__ * must the fate of this PEP be tied to acceptance of a per-interpreter GIL PEP? +* how realistic is the `Accidental De-Immortalizing`_ concern? References From 407efc0d36b834e2cc89f5677f830ebb4f7e2786 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 12:51:05 -0700 Subject: [PATCH 07/26] Clarify how the PEP supports immutability rather than enforcing it. --- pep-0683.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pep-0683.rst b/pep-0683.rst index 2ad1a076abd..6a661c30b93 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -458,6 +458,14 @@ Constraints to persist until runtime finalization. * be careful when immortalizing objects that are not otherwise immutable +Regarding "truly" immutable objects, this PEP doesn't impact the +effective immutability of any objects, other than the per-object +runtime state (e.g. refcount). So whether or not some immortal object +is truly (or even effectively) immutable can only be settled separately +from this proposal. For example, str objects are generally considered +immutable, but ``PyUnicodeObject`` holds some lazily cached data. This +PEP has no influence on how that state affects str immutability. + Immortal Mutable Objects ------------------------ From 7631bdfebc57b9b9fafc30ecddd84231a3d2f620 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 13:21:38 -0700 Subject: [PATCH 08/26] Clarify about the refcount 1. --- pep-0683.rst | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index 6a661c30b93..9a237000e5f 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -432,8 +432,10 @@ to the following questions: As part of this proposal, we must make sure that users can clearly understand on which parts of the refcount behavior they can rely and which are considered implementation details. Specifically, they should -use the existing public refcount-related API and the only refcount value -with any meaning is 0. All other values are considered "not 0". +use the existing public refcount-related API and the only refcount +values with any meaning are 0 and 1. (Some code relies on 1 as an +indicator that then object can be safely modified.) All other values +are considered "not 0 or 1". This information will be clarified in the `documentation `_. @@ -658,11 +660,18 @@ However, we will update the documentation to make public guarantees about refcount behavior more clear. That includes, specifically: * ``Py_INCREF()`` - change "Increment the reference count for object o." - to "Acquire a new reference to object o." + to "Indicate taking a new reference to object o." * ``Py_DECREF()`` - change "Decrement the reference count for object o." - to "Release a reference to object o." + to "Indicate no longer using a previously taken reference to object o." * similar for ``Py_XINCREF()``, ``Py_XDECREF()``, ``Py_NewRef()``, - ``Py_XNewRef()``, ``Py_Clear()``, ``Py_REFCNT()``, and ``Py_SET_REFCNT()`` + ``Py_XNewRef()``, ``Py_Clear()`` +* ``Py_REFCNT()`` - add "The refcounts 0 and 1 have specific meanings + and all others only mean code somewhere is using the object, + regardless of the value. + 0 means the object is not used and will be cleaned up. + 1 means code holds exactly a single reference." +* ``Py_SET_REFCNT()`` - refer to ``Py_REFCNT()`` about how values over 1 + may be substituted with some over value We *may* also add a note about immortal objects to the following, to help reduce any surprise users may have with the change: From d171a3cdb50bbdb115264e8fccbbe651c5bf23d1 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 13:32:31 -0700 Subject: [PATCH 09/26] Clarify about the immortal bit. --- pep-0683.rst | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index 9a237000e5f..c9ee76fd4b5 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -540,14 +540,18 @@ _Py_IMMORTAL_REFCNT We will add two internal constants:: - #define _Py_IMMORTAL_BIT (1LL << (8 * sizeof(Py_ssize_t) - 4)) - #define _Py_IMMORTAL_REFCNT (_Py_IMMORTAL_BIT + (_Py_IMMORTAL_BIT / 2)) - -The refcount for immortal objects will be set to ``_Py_IMMORTAL_REFCNT``. -However, to check if an object is immortal we will compare its refcount -against just the bit:: - - (op->ob_refcnt & _Py_IMMORTAL_BIT) != 0 + _Py_IMMORTAL_BIT - has the top-most available bit set + _Py_IMMORTAL_REFCNT - has the two top-most available bits set + +The actual top-most bit depends on existing uses for refcount bits, +e.g. the sign bit or some GC uses. We will use the highest bit possible +after consideration of existing uses. + +The refcount for immortal objects will be set to ``_Py_IMMORTAL_REFCNT`` +(meaning the value will be halfway between ``_Py_IMMORTAL_BIT`` and the +value at the next highest bit). However, to check if an object is +immortal we will compare (bitwise-and) its refcount against just +`_Py_IMMORTAL_BIT``. The difference means that an immortal object will still be considered immortal, even if somehow its refcount were modified (e.g. by an older From b27fbcbf2bf2103f0e17f57a5cd0f781e4627488 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 13:45:14 -0700 Subject: [PATCH 10/26] Drop some explanation about immortal global objects. --- pep-0683.rst | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index c9ee76fd4b5..fd43e1cbc74 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -581,21 +581,16 @@ will not be affected.) Immortal Global Objects ----------------------- -All objects that we expect to be shared globally (between interpreters) -will be made immortal. That includes the following: +All runtime-global (builtin) objects will be made immortal. +That includes the following: * singletons (``None``, ``True``, ``False``, ``Ellipsis``, ``NotImplemented``) * all static types (e.g. ``PyLong_Type``, ``PyExc_Exception``) * all static objects in ``_PyRuntimeState.global_objects`` (e.g. identifiers, small ints) -All such objects will be immutable. In the case of the static types, -they will only be effectively immutable. ``PyTypeObject`` has some mutable -state (``tp_dict`` and ``tp_subclasses``), but we can work around this -by storing that state on ``PyInterpreterState`` instead of on the -respective static type object. Then the ``__dict__``, etc. getter -will do a lookup on the current interpreter, if appropriate, instead -of using ``tp_dict``. +The question of making them actually immutable (e.g. for +per-interpreter GIL) is not in the scope of this PEP. Object Cleanup -------------- From a0b9d046fcd7554601f1a0c927d794efeb27b2ac Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 13:46:38 -0700 Subject: [PATCH 11/26] Relate cleanup to performance. --- pep-0683.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pep-0683.rst b/pep-0683.rst index fd43e1cbc74..3b472a03077 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -617,6 +617,8 @@ accessible on the runtime state, e.g. in a ``_PyRuntimeState`` or ``PyInterpreterState`` field. We may need to add a tracking mechanism to the runtime state for a small number of objects. +None of the cleanup will have a significant effect on performance. + .. _mitigation: Performance Regression Mitigation From 7666c66fbd2e08fe61c44d4d72d33546c4dcf66a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 13:50:39 -0700 Subject: [PATCH 12/26] Add a note about __del__ and weakrefs. --- pep-0683.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/pep-0683.rst b/pep-0683.rst index 3b472a03077..0ac8afdc288 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -459,6 +459,7 @@ Constraints * be careful when immortalizing objects that we don't actually expect to persist until runtime finalization. * be careful when immortalizing objects that are not otherwise immutable +* ``__del__`` and weakrefs must continue working properly Regarding "truly" immutable objects, this PEP doesn't impact the effective immutability of any objects, other than the per-object From 365ef606a930ba9e397378abb50bdd5f203df202 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 13:59:49 -0700 Subject: [PATCH 13/26] Drop an open question. --- pep-0683.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/pep-0683.rst b/pep-0683.rst index 0ac8afdc288..84e35583942 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -700,7 +700,6 @@ Open Issues * is there any other impact on GC? * `are the copy-on-write benefits real? `__ -* must the fate of this PEP be tied to acceptance of a per-interpreter GIL PEP? * how realistic is the `Accidental De-Immortalizing`_ concern? From fb490f9439705914e4859223f323878f8ea52a4f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 14:02:30 -0700 Subject: [PATCH 14/26] Add a note about GC. --- pep-0683.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pep-0683.rst b/pep-0683.rst index 84e35583942..fb7b430d643 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -579,6 +579,8 @@ API that exposes refcounts (unchanged but may now return large values): (Note that ``_Py_RefTotal`` and ``sys.gettotalrefcount()`` will not be affected.) +Also, immortal objects will not participate in GC. + Immortal Global Objects ----------------------- From 34de9b581af0f52490504e73f74aaf16a5f16694 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 14:33:01 -0700 Subject: [PATCH 15/26] Outline solutions to accidental de-immortalizing. --- pep-0683.rst | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index fb7b430d643..b0b3614377f 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -352,15 +352,27 @@ one object without a corresponding incref. The most likely scenario is the following: A "new" reference to ``None`` is returned by many functions and methods. -The 3.11 runtime will never incref it before giving it to the extension. -However, the extension *will* decref it when done with it. Each time -that exchange happens with the one object, we get one step closer to a -crash. How realistic is it that some form of that exchange happen -a billion times in the lifetime of a Python process on 32-bit? +Unlike with non-immortal objects, the 3.11 runtime will almost never +incref ``None`` before giving it to the extension. However, the +extension *will* decref it when done with it (unless it returns it). +Each time that exchange happens with the one object, we get one step +closer to a crash. - +How realistic is it that some form of that exchange (with a single +object) will happen a billion times in the lifetime of a Python process +on 32-bit? If it is a problem, how could it be addressed? -.. My gut says it is not realistic but that's not very compelling. +As to how realistic, the answer isn't clear currently. However, the +mitigation is simple enough that we can safely proceed under the +assumption that it would be a problem. + +Here are some possible solutions (only needed on 32-bit): + +* periodically reset the refcount for immortal objects + (only enable this if a stable ABI extension is imported?) +* special-case immortal objects in tp_dealloc() for the relevant types + (but not int, due to frequency?) +* provide a runtime flag for disabling immortality Alternate Python Implementations -------------------------------- @@ -700,8 +712,6 @@ https://github.com/python/cpython/pull/19474 Open Issues =========== -* is there any other impact on GC? -* `are the copy-on-write benefits real? `__ * how realistic is the `Accidental De-Immortalizing`_ concern? From 7f5052b1c71c58217a13b121dd986a8a257ed5ca Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 14:33:58 -0700 Subject: [PATCH 16/26] Update the post history. --- pep-0683.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0683.rst b/pep-0683.rst index b0b3614377f..ffab7ad9ad9 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -7,7 +7,7 @@ Type: Standards Track Content-Type: text/x-rst Created: 10-Feb-2022 Python-Version: 3.11 -Post-History: 15-Feb-2022,19-Feb-2022 +Post-History: 15-Feb-2022,19-Feb-2022,28-Feb-2022 Resolution: From 4f1e4fb1cebf26a2a62f4842be3af74e8271ada8 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 14:35:32 -0700 Subject: [PATCH 17/26] Collapse the abstract. --- pep-0683.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index ffab7ad9ad9..839fdada4c3 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -26,10 +26,8 @@ an object as one for which that runtime state will no longer change. Consequently, such an object's refcount will never reach 0, and so the object will never be cleaned up. We call these objects "immortal". (Normally, only a relatively small number of internal objects -will ever be immortal.) - -The fundamental improvement here is that now an object -can be truly immutable. +will ever be immortal.) The fundamental improvement here +is that now an object can be truly immutable. Scope ----- From 27725177bad2e6d65efc29bd141cc757e667a54a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 14:49:16 -0700 Subject: [PATCH 18/26] Tweak the scope section. --- pep-0683.rst | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index 839fdada4c3..580bde24676 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -32,24 +32,25 @@ is that now an object can be truly immutable. Scope ----- -Object immortality is meant to be an internal-only feature. -So this proposal does not include any changes to public API or behavior. -However, this does not prevent us from adding (publicly accessible) -private API to do things like immortalize an object or tell if one -is immortal. +Object immortality is meant to be an internal-only feature. So this +proposal does not include any changes to public API or behavior +(with one exception). As usual, we may still add some private +(yet publicly accessible) API to do things like immortalize an object +or tell if one is immortal. Any effort to expose this feature to users +would need to be proposed separately. -Any effort to expose this feature to users would need to be proposed -separately. There is one exception: refcounting semantics for immortal -objects will differ in some cases from user expectations. This -exception, and the solution, are discussed below. +There is one exception to "no change in behavior": refcounting semantics +for immortal objects will differ in some cases from user expectations. +This exception, and the solution, are discussed below. Most of this PEP focuses on an internal implementation that satisfies the above mandate. However, those implementation details are not meant to be strictly proscriptive. Instead, at the least they are included to help illustrate the technical considerations required by the mandate. -The actual implementation may deviate somewhat. Furthermore, the -acceptability of any implementation detail described below does not -depend on the status of this PEP, unless explicitly.specified. +The actual implementation may deviate somewhat as long as it satisfies +the constraints outlined below. Furthermore, the acceptability of any +specific implementation detail described below does not depend on +the status of this PEP, unless explicitly.specified. For example, the particular details of: From baa1947cb3d160c495ced2b4eb5f66cb16e97b30 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 14:52:31 -0700 Subject: [PATCH 19/26] lint --- pep-0683.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index 580bde24676..65782f97462 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -219,7 +219,7 @@ Backward Compatibility Ideally this internal-only feature would be completely compatible. However, it does involve a change to refcount semantics in some cases. Only immortal objects are affected, but this includes high-use objects -like `None`, `True`, and `False`. +like ``None``, ``True``, and ``False``. Specifically, when an immortal object is involved: @@ -563,7 +563,7 @@ The refcount for immortal objects will be set to ``_Py_IMMORTAL_REFCNT`` (meaning the value will be halfway between ``_Py_IMMORTAL_BIT`` and the value at the next highest bit). However, to check if an object is immortal we will compare (bitwise-and) its refcount against just -`_Py_IMMORTAL_BIT``. +``_Py_IMMORTAL_BIT``. The difference means that an immortal object will still be considered immortal, even if somehow its refcount were modified (e.g. by an older From a6d07a4aede210c062ce8c0ded4b180b39d1ae54 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 15:11:02 -0700 Subject: [PATCH 20/26] Fix a typo. Co-authored-by: Jelle Zijlstra --- pep-0683.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0683.rst b/pep-0683.rst index 65782f97462..78747a33032 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -50,7 +50,7 @@ to help illustrate the technical considerations required by the mandate. The actual implementation may deviate somewhat as long as it satisfies the constraints outlined below. Furthermore, the acceptability of any specific implementation detail described below does not depend on -the status of this PEP, unless explicitly.specified. +the status of this PEP, unless explicitly specified. For example, the particular details of: From 5b2fe4573f6bb0e19a6140df4983611fc500bd1a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 15:11:54 -0700 Subject: [PATCH 21/26] Fix a typo. Co-authored-by: Jelle Zijlstra --- pep-0683.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0683.rst b/pep-0683.rst index 78747a33032..415127426e3 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -325,7 +325,7 @@ Accidental De-Immortalizing 32-bit builds of older stable ABI extensions can take `Accidental Immortality`_ to the next level. -Hypothetically, such an extension could incref an object to value on +Hypothetically, such an extension could incref an object to a value on the next highest bit above the magic refcount value. For example, if the magic value were 2^30 and the initial immortal refcount were thus 2^30 + 2^29 then it would take 2^29 increfs by the extension to reach From 8188ae598ec74850b50cda09ca5b05baf4ba8b49 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 15:12:21 -0700 Subject: [PATCH 22/26] Fix a typo. Co-authored-by: Jelle Zijlstra --- pep-0683.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0683.rst b/pep-0683.rst index 415127426e3..2b7170acee4 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -445,7 +445,7 @@ understand on which parts of the refcount behavior they can rely and which are considered implementation details. Specifically, they should use the existing public refcount-related API and the only refcount values with any meaning are 0 and 1. (Some code relies on 1 as an -indicator that then object can be safely modified.) All other values +indicator that the object can be safely modified.) All other values are considered "not 0 or 1". This information will be clarified in the `documentation `_. From db478a9be9629fcbfab4b5ea9e905d49d630df2e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 16:04:29 -0700 Subject: [PATCH 23/26] Fix the post history. --- pep-0683.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0683.rst b/pep-0683.rst index 2b7170acee4..b8ffaa77f22 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -7,7 +7,7 @@ Type: Standards Track Content-Type: text/x-rst Created: 10-Feb-2022 Python-Version: 3.11 -Post-History: 15-Feb-2022,19-Feb-2022,28-Feb-2022 +Post-History: 15-Feb-2022, 19-Feb-2022, 28-Feb-2022 Resolution: From f6bab1d541bef8e1980cc3e4840c4ae48f6f8155 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 16:13:49 -0700 Subject: [PATCH 24/26] Update the magic bit in examples. --- pep-0683.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index b8ffaa77f22..1d4b291a04c 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -260,7 +260,7 @@ That means it would accidentally never be cleaned up On 64-bit builds, this accidental scenario is so unlikely that we need not worry. Even if done deliberately by using ``Py_INCREF()`` in a tight loop and each iteration only took 1 CPU cycle, it would take -2^60 cycles (on a 64-bit processor). At a fast 5 GHz that would +2^60 cycles (if the immortal bit were 2^60). At a fast 5 GHz that would still take nearly 250,000,000 seconds (over 2,500 days)! Also note that it is doubly unlikely to be a problem because it wouldn't @@ -275,11 +275,11 @@ same thing could be done efficiently using ``Py_SET_REFCNT()`` though that would be even less of an accident.) At that point we don't consider it a concern of this proposal. -On 32-bit builds it isn't so obvious. The magic refcount would be 2^28. -Using the same specs as above, it would take roughly 1 second to -accidentally immortalize an object. Under reasonable conditions, it -is still highly unlikely that an object be accidentally immortalized. -It would have to meet these criteria: +On 32-bit builds it isn't so obvious. Let's say the magic refcount +were 2^30. Using the same specs as above, it would take roughly +4 seconds to accidentally immortalize an object. Under reasonable +conditions, it is still highly unlikely that an object be accidentally +immortalized. It would have to meet these criteria: * targeting a non-immortal object (so not one of the high-use builtins) * the extension increfs without a corresponding decref @@ -288,7 +288,7 @@ It would have to meet these criteria: Under those conditions it would reach accidental immortality (on 32-bit) in, at most, a year if it averaged at least one of those increfs every -158 seconds on that hypothetical workstation. Of course, then it would +40 seconds on that hypothetical workstation. Of course, then it would have to run through the same number of (now noop-ing) decrefs before that one object would be effectively leaking. This is highly unlikely, especially because we assume no decrefs. @@ -552,7 +552,7 @@ _Py_IMMORTAL_REFCNT We will add two internal constants:: - _Py_IMMORTAL_BIT - has the top-most available bit set + _Py_IMMORTAL_BIT - has the top-most available bit set (e.g. 2^62) _Py_IMMORTAL_REFCNT - has the two top-most available bits set The actual top-most bit depends on existing uses for refcount bits, From c8a2d41928db2a5438582cd6d8121eca0e87f3f2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 16:36:58 -0700 Subject: [PATCH 25/26] Clarify an exmaple. --- pep-0683.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index 1d4b291a04c..bcd90a035b2 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -286,12 +286,11 @@ immortalized. It would have to meet these criteria: (e.g. returns from a function or method) * no other code decrefs the object in the meantime -Under those conditions it would reach accidental immortality (on 32-bit) -in, at most, a year if it averaged at least one of those increfs every -40 seconds on that hypothetical workstation. Of course, then it would -have to run through the same number of (now noop-ing) decrefs before -that one object would be effectively leaking. This is highly unlikely, -especially because we assume no decrefs. +Even at a much less frequent rate incref it would not take long to reach +accidental immortality (on 32-bit). However, then it would have to run +through the same number of (now noop-ing) decrefs before that one object +would be effectively leaking. This is highly unlikely, especially because +the calculations assume no decrefs. Furthermore, this isn't all that different from how such 32-bit extensions can already incref an object past 2^31 and turn the refcount negative. From 4ed4d4463c339c6ef324b9be6c3cc9e02368721a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Feb 2022 17:53:47 -0700 Subject: [PATCH 26/26] Drop an outdated note. --- pep-0683.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/pep-0683.rst b/pep-0683.rst index bcd90a035b2..a7a2e2dda1f 100644 --- a/pep-0683.rst +++ b/pep-0683.rst @@ -504,9 +504,6 @@ it immortal, we no longer incur the extra overhead during incref/decref. We explore this idea further in the `mitigation`_ section below. -(Note that we are still investigating the impact on GC -of immortalizing containers.) - Implicitly Immortal Objects ---------------------------