python-peps/pep-0683/index.html

1018 lines
72 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>PEP 683 Immortal Objects, Using a Fixed Refcount | peps.python.org</title>
<link rel="shortcut icon" href="../_static/py.png">
<link rel="canonical" href="https://peps.python.org/pep-0683/">
<link rel="stylesheet" href="../_static/style.css" type="text/css">
<link rel="stylesheet" href="../_static/mq.css" type="text/css">
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light">
<link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark">
<link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss">
<meta property="og:title" content='PEP 683 Immortal Objects, Using a Fixed Refcount | peps.python.org'>
<meta property="og:description" content="Currently the CPython runtime maintains a small amount of mutable state in the 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, esp...">
<meta property="og:type" content="website">
<meta property="og:url" content="https://peps.python.org/pep-0683/">
<meta property="og:site_name" content="Python Enhancement Proposals (PEPs)">
<meta property="og:image" content="https://peps.python.org/_static/og-image.png">
<meta property="og:image:alt" content="Python PEPs">
<meta property="og:image:width" content="200">
<meta property="og:image:height" content="200">
<meta name="description" content="Currently the CPython runtime maintains a small amount of mutable state in the 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, esp...">
<meta name="theme-color" content="#3776ab">
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all">
<title>Following system colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"></circle>
<path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path>
</svg>
</symbol>
<symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all">
<title>Selected dark colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path>
</svg>
</symbol>
<symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all">
<title>Selected light colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
</svg>
<script>
document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto"
</script>
<section id="pep-page-section">
<header>
<h1>Python Enhancement Proposals</h1>
<ul class="breadcrumbs">
<li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> &raquo; </li>
<li><a href="../pep-0000/">PEP Index</a> &raquo; </li>
<li>PEP 683</li>
</ul>
<button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())">
<svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg>
<span class="visually-hidden">Toggle light / dark / auto colour theme</span>
</button>
</header>
<article>
<section id="pep-content">
<h1 class="page-title">PEP 683 Immortal Objects, Using a Fixed Refcount</h1>
<dl class="rfc2822 field-list simple">
<dt class="field-odd">Author<span class="colon">:</span></dt>
<dd class="field-odd">Eric Snow &lt;ericsnowcurrently&#32;&#97;t&#32;gmail.com&gt;, Eddie Elizondo &lt;eduardo.elizondorueda&#32;&#97;t&#32;gmail.com&gt;</dd>
<dt class="field-even">Discussions-To<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/18183">Discourse thread</a></dd>
<dt class="field-odd">Status<span class="colon">:</span></dt>
<dd class="field-odd"><abbr title="Accepted and implementation complete, or no longer active">Final</abbr></dd>
<dt class="field-even">Type<span class="colon">:</span></dt>
<dd class="field-even"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd>
<dt class="field-odd">Created<span class="colon">:</span></dt>
<dd class="field-odd">10-Feb-2022</dd>
<dt class="field-even">Python-Version<span class="colon">:</span></dt>
<dd class="field-even">3.12</dd>
<dt class="field-odd">Post-History<span class="colon">:</span></dt>
<dd class="field-odd"><a class="reference external" href="https://mail.python.org/archives/list/python-dev&#64;python.org/thread/TPLEYDCXFQ4AMTW6F6OQFINSIFYBRFCR/" title="Python-Dev thread">16-Feb-2022</a>,
<a class="reference external" href="https://mail.python.org/archives/list/python-dev&#64;python.org/thread/KDAR6CCMPOX36GQJUDWHQBKRD5USNV3B/" title="Python-Dev thread">19-Feb-2022</a>,
<a class="reference external" href="https://mail.python.org/archives/list/python-dev&#64;python.org/thread/MI22URMVKC63OFMZTALHFZKAKVGAT4UF/" title="Python-Dev thread">28-Feb-2022</a>,
<a class="reference external" href="https://discuss.python.org/t/18183" title="Discourse thread">12-Aug-2022</a></dd>
<dt class="field-even">Resolution<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/18183/26">Discourse message</a></dd>
</dl>
<hr class="docutils" />
<section id="contents">
<details><summary>Table of Contents</summary><ul class="simple">
<li><a class="reference internal" href="#pep-acceptance-conditions">PEP Acceptance Conditions</a></li>
<li><a class="reference internal" href="#abstract">Abstract</a><ul>
<li><a class="reference internal" href="#scope">Scope</a></li>
<li><a class="reference internal" href="#implementation-summary">Implementation Summary</a></li>
</ul>
</li>
<li><a class="reference internal" href="#motivation">Motivation</a><ul>
<li><a class="reference internal" href="#reducing-cpu-cache-invalidation">Reducing CPU Cache Invalidation</a></li>
<li><a class="reference internal" href="#avoiding-data-races">Avoiding Data Races</a></li>
<li><a class="reference internal" href="#avoiding-copy-on-write">Avoiding Copy-on-Write</a></li>
</ul>
</li>
<li><a class="reference internal" href="#rationale">Rationale</a></li>
<li><a class="reference internal" href="#impact">Impact</a><ul>
<li><a class="reference internal" href="#benefits">Benefits</a></li>
<li><a class="reference internal" href="#performance">Performance</a></li>
<li><a class="reference internal" href="#backward-compatibility">Backward Compatibility</a><ul>
<li><a class="reference internal" href="#accidental-immortality">Accidental Immortality</a></li>
<li><a class="reference internal" href="#stable-abi">Stable ABI</a></li>
<li><a class="reference internal" href="#accidental-de-immortalizing">Accidental De-Immortalizing</a></li>
</ul>
</li>
<li><a class="reference internal" href="#alternate-python-implementations">Alternate Python Implementations</a></li>
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
<li><a class="reference internal" href="#maintainability">Maintainability</a></li>
</ul>
</li>
<li><a class="reference internal" href="#specification">Specification</a><ul>
<li><a class="reference internal" href="#public-refcount-details">Public Refcount Details</a></li>
<li><a class="reference internal" href="#constraints">Constraints</a></li>
<li><a class="reference internal" href="#immortal-mutable-objects">Immortal Mutable Objects</a></li>
<li><a class="reference internal" href="#implicitly-immortal-objects">Implicitly Immortal Objects</a></li>
<li><a class="reference internal" href="#un-immortalizing-objects">Un-Immortalizing Objects</a></li>
<li><a class="reference internal" href="#py-immortal-refcnt">_Py_IMMORTAL_REFCNT</a></li>
<li><a class="reference internal" href="#affected-api">Affected API</a></li>
<li><a class="reference internal" href="#immortal-global-objects">Immortal Global Objects</a></li>
<li><a class="reference internal" href="#object-cleanup">Object Cleanup</a></li>
<li><a class="reference internal" href="#performance-regression-mitigations">Performance Regression Mitigations</a><ul>
<li><a class="reference internal" href="#at-the-end-of-runtime-init-mark-all-objects-as-immortal">at the end of runtime init, mark all objects as immortal</a></li>
<li><a class="reference internal" href="#drop-unnecessary-hard-coded-refcount-operations">drop unnecessary hard-coded refcount operations</a></li>
<li><a class="reference internal" href="#specialize-for-immortal-objects-in-the-eval-loop">specialize for immortal objects in the eval loop</a></li>
<li><a class="reference internal" href="#other-possibilities">other possibilities</a></li>
</ul>
</li>
<li><a class="reference internal" href="#solutions-for-accidental-de-immortalization">Solutions for Accidental De-Immortalization</a></li>
<li><a class="reference internal" href="#documentation">Documentation</a></li>
</ul>
</li>
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
<li><a class="reference internal" href="#open-issues">Open Issues</a></li>
<li><a class="reference internal" href="#references">References</a><ul>
<li><a class="reference internal" href="#prior-art">Prior Art</a></li>
<li><a class="reference internal" href="#discussions">Discussions</a></li>
<li><a class="reference internal" href="#runtime-object-state">Runtime Object State</a></li>
<li><a class="reference internal" href="#reference-counting-with-cyclic-garbage-collection">Reference Counting, with Cyclic Garbage Collection</a></li>
</ul>
</li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
</details></section>
<div class="pep-banner canonical-doc sticky-banner admonition important">
<p class="admonition-title">Important</p>
<p>This PEP is a historical document. The up-to-date, canonical documentation can now be found at <a class="reference external" href="https://docs.python.org/3/glossary.html#term-reference-count" title="(in Python v3.12)"><span class="xref std std-term">reference count</span></a>.</p>
<p class="close-button">×</p>
<p>See <a class="pep reference internal" href="../pep-0001/" title="PEP 1 PEP Purpose and Guidelines">PEP 1</a> for how to propose changes.</p>
</div>
<section id="pep-acceptance-conditions">
<h2><a class="toc-backref" href="#pep-acceptance-conditions" role="doc-backlink">PEP Acceptance Conditions</a></h2>
<p>The PEP was accepted with conditions:</p>
<ul class="simple">
<li>the primary proposal in <a class="reference internal" href="#solutions-for-accidental-de-immortalization">Solutions for Accidental De-Immortalization</a>
(reset the immortal refcount in <code class="docutils literal notranslate"><span class="pre">tp_dealloc()</span></code>) was applied</li>
<li>types without this were not immortalized (in CPythons code)</li>
<li>the PEP was updated with final benchmark results once
the implementation is finalized (confirming the change is worthwhile)</li>
</ul>
</section>
<section id="abstract">
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
<p>Currently the CPython runtime maintains a
<a class="reference internal" href="#runtime-object-state">small amount of mutable state</a> in the
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
Pythons scalability.</p>
<p>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 objects refcount will never reach 0, and thus
the object will never be cleaned up (except when the runtime knows
its safe to do so, like during runtime finalization).
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.</p>
<section id="scope">
<h3><a class="toc-backref" href="#scope" role="doc-backlink">Scope</a></h3>
<p>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.</p>
<p>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.</p>
<p>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 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.</p>
<p>For example, the particular details of:</p>
<ul class="simple">
<li>how to mark something as immortal</li>
<li>how to recognize something as immortal</li>
<li>which subset of functionally immortal objects are marked as immortal</li>
<li>which memory-management activities are skipped or modified for immortal objects</li>
</ul>
<p>are not only CPython-specific but are also private implementation
details that are expected to change in subsequent versions.</p>
</section>
<section id="implementation-summary">
<h3><a class="toc-backref" href="#implementation-summary" role="doc-backlink">Implementation Summary</a></h3>
<p>Heres a high-level look at the implementation:</p>
<p>If an objects 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. The runtime will now be explicitly responsible for deallocating
all immortal objects during finalization, unless statically allocated.
(See <a class="reference internal" href="#object-cleanup">Object Cleanup</a> below.)</p>
<p>Aside from the change to refcounting semantics, there is one other
possible negative impact to consider. The threshold for an “acceptable”
performance penalty for immortal objects is 2% (the consensus at the
2022 Language Summit). A naive implementation of the approach described
below makes CPython roughly 4% slower. However, the implementation
is ~performance-neutral~ once known mitigations are applied.</p>
<p>TODO: Update the performance impact for the latest branch
(both for GCC and for clang).</p>
</section>
</section>
<section id="motivation">
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
<p>As noted above, currently all objects are effectively mutable. That
includes “immutable” objects like <code class="docutils literal notranslate"><span class="pre">str</span></code> instances. This is because
every objects refcount is frequently modified as the object is used
during execution. This is especially significant for a number of
commonly used global (builtin) objects, e.g. <code class="docutils literal notranslate"><span class="pre">None</span></code>. Such objects
are used a lot, both in Python code and internally. That adds up to
a consistent high volume of refcount changes.</p>
<p>The effective mutability of all Python objects has a concrete impact
on parts of the Python community, e.g. projects that aim for
scalability like Instagram or the effort to make the GIL
per-interpreter. Below we describe several ways in which refcount
modification has a real negative effect on such projects.
None of that would happen for objects that are truly immutable.</p>
<section id="reducing-cpu-cache-invalidation">
<h3><a class="toc-backref" href="#reducing-cpu-cache-invalidation" role="doc-backlink">Reducing CPU Cache Invalidation</a></h3>
<p>Every modification of a refcount causes the corresponding CPU cache
line to be invalidated. This has a number of effects.</p>
<p>For one, the write must be propagated to other cache levels
and to main memory. This has small effect on all Python programs.
Immortal objects would provide a slight relief in that regard.</p>
<p>On top of that, multi-core applications pay a price. If two threads
(running simultaneously on distinct cores) are interacting with the
same object (e.g. <code class="docutils literal notranslate"><span class="pre">None</span></code>) then they will end up invalidating each
others caches with each incref and decref. This is true even for
otherwise immutable objects like <code class="docutils literal notranslate"><span class="pre">True</span></code>, <code class="docutils literal notranslate"><span class="pre">0</span></code>, and <code class="docutils literal notranslate"><span class="pre">str</span></code> instances.
CPythons GIL helps reduce this effect, since only one thread runs at a
time, but it doesnt completely eliminate the penalty.</p>
</section>
<section id="avoiding-data-races">
<h3><a class="toc-backref" href="#avoiding-data-races" role="doc-backlink">Avoiding Data Races</a></h3>
<p>Speaking of multi-core, we are considering making the GIL
a per-interpreter lock, which would enable true multi-core parallelism.
Among other things, the GIL currently protects against races between
multiple concurrent threads that may incref or decref the same object.
Without a shared GIL, two running interpreters could not safely share
any objects, even otherwise immutable ones like <code class="docutils literal notranslate"><span class="pre">None</span></code>.</p>
<p>This means that, to have a per-interpreter GIL, each interpreter must
have its own copy of <em>every</em> object. That includes the singletons and
static types. We have a viable strategy for that but it will require
a meaningful amount of extra effort and extra complexity.</p>
<p>The alternative is to ensure that all shared objects are truly immutable.
There would be no races because there would be no modification. This
is something that the immortality proposed here would enable for
otherwise immutable objects. With immortal objects,
support for a per-interpreter GIL
becomes much simpler.</p>
</section>
<section id="avoiding-copy-on-write">
<h3><a class="toc-backref" href="#avoiding-copy-on-write" role="doc-backlink">Avoiding Copy-on-Write</a></h3>
<p>For some applications it makes sense to get the application into
a desired initial state and then fork the process for each worker.
This can result in a large performance improvement, especially
memory usage. Several enterprise Python users (e.g. Instagram,
YouTube) have taken advantage of this. However, the above
refcount semantics drastically reduce the benefits and
have led to some sub-optimal workarounds.</p>
<p>Also note that “fork” isnt the only operating system mechanism
that uses copy-on-write semantics. Another example is <code class="docutils literal notranslate"><span class="pre">mmap</span></code>.
Any such utility will potentially benefit from fewer copy-on-writes
when immortal objects are involved, when compared to using only
“mortal” objects.</p>
</section>
</section>
<section id="rationale">
<h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
<p>The proposed solution is obvious enough that both of this proposals
authors came to the same conclusion (and implementation, more or less)
independently. The Pyston project <a class="reference external" href="https://mail.python.org/archives/list/python-dev&#64;python.org/message/JLHRTBJGKAENPNZURV4CIJSO6HI62BV3/">uses a similar approach</a>.
Other designs were also considered. Several possibilities have also
been discussed on python-dev in past years.</p>
<p>Alternatives include:</p>
<ul class="simple">
<li>use a high bit to mark “immortal” but do not change <code class="docutils literal notranslate"><span class="pre">Py_INCREF()</span></code></li>
<li>add an explicit flag to objects</li>
<li>implement via the type (<code class="docutils literal notranslate"><span class="pre">tp_dealloc()</span></code> is a no-op)</li>
<li>track via the objects type object</li>
<li>track with a separate table</li>
</ul>
<p>Each of the above makes objects immortal, but none of them address
the performance penalties from refcount modification described above.</p>
<p>In the case of per-interpreter GIL, the only realistic alternative
is to move all global objects into <code class="docutils literal notranslate"><span class="pre">PyInterpreterState</span></code> and add
one or more lookup functions to access them. Then wed have to
add some hacks to the C-API to preserve compatibility for the
may objects exposed there. The story is much, much simpler
with immortal objects.</p>
</section>
<section id="impact">
<h2><a class="toc-backref" href="#impact" role="doc-backlink">Impact</a></h2>
<section id="benefits">
<h3><a class="toc-backref" href="#benefits" role="doc-backlink">Benefits</a></h3>
<p>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
types, as well as objects exposed by the public C-API.</p>
<p>In general, a strong immutability guarantee for objects enables Python
applications to scale better, particularly in
<a class="reference external" href="Facebook">multi-process deployments</a>. This is because they can then
leverage multi-core parallelism without such a significant tradeoff in
memory usage as they now have. The cases we just described, as well as
those described above in <a class="reference internal" href="#motivation">Motivation</a>, reflect this improvement.</p>
</section>
<section id="performance">
<h3><a class="toc-backref" href="#performance" role="doc-backlink">Performance</a></h3>
<p>A naive implementation shows <a class="reference external" href="https://github.com/python/cpython/pull/19474#issuecomment-1502245844">a 2% slowdown</a> (3% with MSVC).
We have demonstrated a return to ~performance-neutral~ with a handful
of basic mitigations applied. See the <a class="reference internal" href="#mitigations">mitigations</a> section below.</p>
<p>On the positive side, immortal objects save a significant amount of
memory when used <a class="reference external" href="Facebook">with a pre-fork model</a>. Also, immortal
objects provide opportunities for specialization in the eval loop that
would improve performance.</p>
</section>
<section id="backward-compatibility">
<h3><a class="toc-backref" href="#backward-compatibility" role="doc-backlink">Backward Compatibility</a></h3>
<p>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 <code class="docutils literal notranslate"><span class="pre">None</span></code>, <code class="docutils literal notranslate"><span class="pre">True</span></code>, and <code class="docutils literal notranslate"><span class="pre">False</span></code>.</p>
<p>Specifically, when an immortal object is involved:</p>
<ul class="simple">
<li>code that inspects the refcount will see a really, really large value</li>
<li>the new noop behavior may break code that:<ul>
<li>depends specifically on the refcount to always increment or decrement
(or have a specific value from <code class="docutils literal notranslate"><span class="pre">Py_SET_REFCNT()</span></code>)</li>
<li>relies on any specific refcount value, other than 0 or 1</li>
<li>directly manipulates the refcount to store extra information there</li>
</ul>
</li>
<li>in 32-bit pre-3.12 <a class="reference internal" href="#stable-abi">Stable ABI</a> extensions,
objects may leak due to <a class="reference internal" href="#accidental-immortality">Accidental Immortality</a></li>
<li>such extensions may crash due to <a class="reference internal" href="#accidental-de-immortalizing">Accidental De-Immortalizing</a></li>
</ul>
<p>Again, those changes in behavior only apply to immortal objects,
not the vast majority of objects a user will use. 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. So the overall impact should be small.</p>
<p>Also note that code which checks for refleaks should keep working fine,
unless it checks for hard-coded small values relative to some immortal
object. The problems noticed by <a class="reference external" href="https://mail.python.org/archives/list/python-dev&#64;python.org/message/JLHRTBJGKAENPNZURV4CIJSO6HI62BV3/">Pyston</a> shouldnt apply here since
we do not modify the refcount.</p>
<p>See <a class="reference internal" href="#public-refcount-details">Public Refcount Details</a> below for further discussion.</p>
<section id="accidental-immortality">
<h4><a class="toc-backref" href="#accidental-immortality" role="doc-backlink">Accidental Immortality</a></h4>
<p>Hypothetically, a non-immortal object could be increfed so much
that it reaches the magic value needed to be considered immortal.
That means it would never be decrefed all the way back to 0, so it
would accidentally leak (never be cleaned up).</p>
<p>With 64-bit refcounts, this accidental scenario is so unlikely that
we need not worry. Even if done deliberately by using <code class="docutils literal notranslate"><span class="pre">Py_INCREF()</span></code>
in a tight loop and each iteration only took 1 CPU cycle, it would take
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)!</p>
<p>Also note that it is doubly unlikely to be a problem because it wouldnt
matter until the refcount would have gotten back to 0 and the object
cleaned up. So any object that hit that magic “immortal” refcount value
would have to be decrefed that many times again before the change
in behavior would be noticed.</p>
<p>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 <code class="docutils literal notranslate"><span class="pre">Py_SET_REFCNT()</span></code> though
that would be even less of an accident.) At that point we dont
consider it a concern of this proposal.</p>
<p>On builds with much smaller maximum refcounts, like 32-bit platforms,
the consequences arent so obvious. Lets 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:</p>
<ul class="simple">
<li>targeting a non-immortal object (so not one of the high-use builtins)</li>
<li>the extension increfs without a corresponding decref
(e.g. returns from a function or method)</li>
<li>no other code decrefs the object in the meantime</li>
</ul>
<p>Even at a much less frequent rate 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.</p>
<p>Furthermore, this isnt 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.</p>
<p>Between all of the above cases, the proposal doesnt consider
accidental immortality a problem.</p>
</section>
<section id="stable-abi">
<h4><a class="toc-backref" href="#stable-abi" role="doc-backlink">Stable ABI</a></h4>
<p>The implementation approach described in this PEP is compatible
with extensions compiled to the stable ABI (with the exception
of <a class="reference internal" href="#accidental-immortality">Accidental Immortality</a> and <a class="reference internal" href="#accidental-de-immortalizing">Accidental De-Immortalizing</a>).
Due to the nature of the stable ABI, unfortunately, such extensions
use versions of <code class="docutils literal notranslate"><span class="pre">Py_INCREF()</span></code>, etc. that directly modify the objects
<code class="docutils literal notranslate"><span class="pre">ob_refcnt</span></code> field. This will invalidate all the performance benefits
of immortal objects.</p>
<p>However, we do ensure that immortal objects (mostly) stay immortal
in that situation. We set the initial refcount of immortal objects to
a value for which we can identify the object as immortal and which
continues to do so even if the refcount is modified by an extension.
(For example, suppose we used one of the high refcount bits to indicate
that an object was immortal. We would set the initial refcount to a
higher value that still matches the bit, like halfway to the next bit.
See <a class="reference internal" href="#py-immortal-refcnt">_Py_IMMORTAL_REFCNT</a>.)
At worst, objects in that situation would feel the effects
described in the <a class="reference internal" href="#motivation">Motivation</a> section. Even then
the overall impact is unlikely to be significant.</p>
</section>
<section id="accidental-de-immortalizing">
<h4><a class="toc-backref" href="#accidental-de-immortalizing" role="doc-backlink">Accidental De-Immortalizing</a></h4>
<p>32-bit builds of older stable ABI extensions can take
<a class="reference internal" href="#accidental-immortality">Accidental Immortality</a> to the next level.</p>
<p>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
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.)</p>
<p>The more problematic case is where such a 32-bit stable ABI extension
goes crazy decrefing 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 <code class="docutils literal notranslate"><span class="pre">None</span></code> could be
made mortal and subject to decref. That still wouldnt be a problem
until somehow the decrefs continue on that object until it reaches 0.
For statically allocated immortal objects, like <code class="docutils literal notranslate"><span class="pre">None</span></code>, the extension
would crash the process if it tried to dealloc the object. For any
other immortal objects, the dealloc might be okay. However, there
might be runtime code expecting the formerly-immortal object to be
around forever. That code would probably crash.</p>
<p>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:</p>
<p>A “new” reference to <code class="docutils literal notranslate"><span class="pre">None</span></code> is returned by many functions and methods.
Unlike with non-immortal objects, the 3.12 runtime will basically never
incref <code class="docutils literal notranslate"><span class="pre">None</span></code> before giving it to the extension. However, the
extension <em>will</em> 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.</p>
<p>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?</p>
<p>As to how realistic, the answer isnt clear currently. However, the
mitigation is simple enough that we can safely proceed under the
assumption that it would not be a problem.</p>
<p>We look at possible solutions
<a class="reference external" href="SolutionsforAccidentalDe-Immortalization">later on</a>.</p>
</section>
</section>
<section id="alternate-python-implementations">
<h3><a class="toc-backref" href="#alternate-python-implementations" role="doc-backlink">Alternate Python Implementations</a></h3>
<p>This proposal is CPython-specific. However, it does relate to the
behavior of the C-API, which may affect other Python implementations.
Consequently, the effect of changed behavior described in
<a class="reference internal" href="#backward-compatibility">Backward Compatibility</a> above also applies here (e.g. if another
implementation is tightly coupled to specific refcount values, other
than 0, or on exactly how refcounts change, then they may impacted).</p>
</section>
<section id="security-implications">
<h3><a class="toc-backref" href="#security-implications" role="doc-backlink">Security Implications</a></h3>
<p>This feature has no known impact on security.</p>
</section>
<section id="maintainability">
<h3><a class="toc-backref" href="#maintainability" role="doc-backlink">Maintainability</a></h3>
<p>This is not a complex feature so it should not cause much mental
overhead for maintainers. The basic implementation doesnt touch
much code so it should have much impact on maintainability. There
may be some extra complexity due to performance penalty mitigation.
However, that should be limited to where we immortalize all objects
post-init and later explicitly deallocate them during runtime
finalization. The code for this should be relatively concentrated.</p>
</section>
</section>
<section id="specification">
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
<p>The approach involves these fundamental changes:</p>
<ul class="simple">
<li>add <a class="reference internal" href="#py-immortal-refcnt">_Py_IMMORTAL_REFCNT</a> (the magic value) to the internal C-API</li>
<li>update <code class="docutils literal notranslate"><span class="pre">Py_INCREF()</span></code> and <code class="docutils literal notranslate"><span class="pre">Py_DECREF()</span></code> to no-op for objects
that match the magic refcount</li>
<li>do the same for any other API that modifies the refcount</li>
<li>stop modifying <code class="docutils literal notranslate"><span class="pre">PyGC_Head</span></code> for immortal GC objects (“containers”)</li>
<li>ensure that all immortal objects are cleaned up during
runtime finalization</li>
</ul>
<p>Then setting any objects refcount to <code class="docutils literal notranslate"><span class="pre">_Py_IMMORTAL_REFCNT</span></code>
makes it immortal.</p>
<p>(There are other minor, internal changes which are not described here.)</p>
<p>In the following sub-sections we dive into the most significant details.
First we will cover some conceptual topics, followed by more concrete
aspects like specific affected APIs.</p>
<section id="public-refcount-details">
<h3><a class="toc-backref" href="#public-refcount-details" role="doc-backlink">Public Refcount Details</a></h3>
<p>In <a class="reference internal" href="#backward-compatibility">Backward Compatibility</a> we introduced possible ways that user code
might be broken by the change in this proposal. Any contributing
misunderstanding by users is likely due in large part to the names of
the refcount-related API and to how the documentation explains those
API (and refcounting in general).</p>
<p>Between the names and the docs, we can clearly see answers
to the following questions:</p>
<ul class="simple">
<li>what behavior do users expect?</li>
<li>what guarantees do we make?</li>
<li>do we indicate how to interpret the refcount value they receive?</li>
<li>what are the use cases under which a user would set an objects
refcount to a specific value?</li>
<li>are users setting the refcount of objects they did not create?</li>
</ul>
<p>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
values with any meaning are 0 and 1. (Some code relies on 1 as an
indicator that the object can be safely modified.) All other values
are considered “not 0 or 1”.</p>
<p>This information will be clarified
in the <a class="reference internal" href="#documentation">documentation</a>.</p>
<p>Arguably, the existing refcount-related API should be modified to reflect
what we want users to expect. Something like the following:</p>
<ul class="simple">
<li><code class="docutils literal notranslate"><span class="pre">Py_INCREF()</span></code> -&gt; <code class="docutils literal notranslate"><span class="pre">Py_ACQUIRE_REF()</span></code> (or only support <code class="docutils literal notranslate"><span class="pre">Py_NewRef()</span></code>)</li>
<li><code class="docutils literal notranslate"><span class="pre">Py_DECREF()</span></code> -&gt; <code class="docutils literal notranslate"><span class="pre">Py_RELEASE_REF()</span></code></li>
<li><code class="docutils literal notranslate"><span class="pre">Py_REFCNT()</span></code> -&gt; <code class="docutils literal notranslate"><span class="pre">Py_HAS_REFS()</span></code></li>
<li><code class="docutils literal notranslate"><span class="pre">Py_SET_REFCNT()</span></code> -&gt; <code class="docutils literal notranslate"><span class="pre">Py_RESET_REFS()</span></code> and <code class="docutils literal notranslate"><span class="pre">Py_SET_NO_REFS()</span></code></li>
</ul>
<p>However, such a change is not a part of this proposal. It is included
here to demonstrate the tighter focus for user expectations that would
benefit this change.</p>
</section>
<section id="constraints">
<h3><a class="toc-backref" href="#constraints" role="doc-backlink">Constraints</a></h3>
<ul class="simple">
<li>ensure that otherwise immutable objects can be truly immutable</li>
<li>minimize performance penalty for normal Python use cases</li>
<li>be careful when immortalizing objects that we dont actually expect
to persist until runtime finalization.</li>
<li>be careful when immortalizing objects that are not otherwise immutable</li>
<li><code class="docutils literal notranslate"><span class="pre">__del__</span></code> and weakrefs must continue working properly</li>
</ul>
<p>Regarding “truly” immutable objects, this PEP doesnt 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 <code class="docutils literal notranslate"><span class="pre">PyUnicodeObject</span></code> holds some lazily cached data. This
PEP has no influence on how that state affects str immutability.</p>
</section>
<section id="immortal-mutable-objects">
<h3><a class="toc-backref" href="#immortal-mutable-objects" role="doc-backlink">Immortal Mutable Objects</a></h3>
<p>Any object can be marked as immortal. We do not propose any
restrictions or checks. However, in practice the value of making an
object immortal relates to its mutability and depends on the likelihood
it would be used for a sufficient portion of the applications lifetime.
Marking a mutable object as immortal can make sense in some situations.</p>
<p>Many of the use cases for immortal objects center on immutability, so
that threads can safely and efficiently share such objects without
locking. For this reason a mutable object, like a dict or list, would
never be shared (and thus no immortality). However, immortality may
be appropriate if there is sufficient guarantee that the normally
mutable object wont actually be modified.</p>
<p>On the other hand, some mutable objects will never be shared between
threads (at least not without a lock like the GIL). In some cases it
may be practical to make some of those immortal too. For example,
<code class="docutils literal notranslate"><span class="pre">sys.modules</span></code> is a per-interpreter dict that we do not expect to
ever get freed until the corresponding interpreter is finalized
(assuming it isnt replaced). By making it immortal, we would
no longer incur the extra overhead during incref/decref.</p>
<p>We explore this idea further in the <a class="reference internal" href="#mitigations">mitigations</a> section below.</p>
</section>
<section id="implicitly-immortal-objects">
<h3><a class="toc-backref" href="#implicitly-immortal-objects" role="doc-backlink">Implicitly Immortal Objects</a></h3>
<p>If an immortal object holds a reference to a normal (mortal) object
then that held object is effectively immortal. This is because that
objects refcount can never reach 0 until the immortal object releases
it.</p>
<p>Examples:</p>
<ul class="simple">
<li>containers like <code class="docutils literal notranslate"><span class="pre">dict</span></code> and <code class="docutils literal notranslate"><span class="pre">list</span></code></li>
<li>objects that hold references internally like <code class="docutils literal notranslate"><span class="pre">PyTypeObject</span></code> with
its <code class="docutils literal notranslate"><span class="pre">tp_subclasses</span></code> and <code class="docutils literal notranslate"><span class="pre">tp_weaklist</span></code></li>
<li>an objects type (held in <code class="docutils literal notranslate"><span class="pre">ob_type</span></code>)</li>
</ul>
<p>Such held objects are thus implicitly immortal for as long as they are
held. In practice, this should have no real consequences since it
really isnt a change in behavior. The only difference is that the
immortal object (holding the reference) doesnt ever get cleaned up.</p>
<p>We do not propose that such implicitly immortal objects be changed
in any way. They should not be explicitly marked as immortal just
because they are held by an immortal object. That would provide
no advantage over doing nothing.</p>
</section>
<section id="un-immortalizing-objects">
<h3><a class="toc-backref" href="#un-immortalizing-objects" role="doc-backlink">Un-Immortalizing Objects</a></h3>
<p>This proposal does not include any mechanism for taking an immortal
object and returning it to a “normal” condition. Currently there
is no need for such an ability.</p>
<p>On top of that, the obvious approach is to simply set the refcount
to a small value. However, at that point there is no way in knowing
which value would be safe. Ideally wed set it to the value that it
would have been if it hadnt been made immortal. However, that value
will have long been lost. Hence the complexities involved make it less
likely that an object could safely be un-immortalized, even if we
had a good reason to do so.</p>
</section>
<section id="py-immortal-refcnt">
<h3><a class="toc-backref" href="#py-immortal-refcnt" role="doc-backlink">_Py_IMMORTAL_REFCNT</a></h3>
<p>We will add two internal constants:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">_Py_IMMORTAL_BIT</span> <span class="o">-</span> <span class="n">has</span> <span class="n">the</span> <span class="n">top</span><span class="o">-</span><span class="n">most</span> <span class="n">available</span> <span class="n">bit</span> <span class="nb">set</span> <span class="p">(</span><span class="n">e</span><span class="o">.</span><span class="n">g</span><span class="o">.</span> <span class="mi">2</span><span class="o">^</span><span class="mi">62</span><span class="p">)</span>
<span class="n">_Py_IMMORTAL_REFCNT</span> <span class="o">-</span> <span class="n">has</span> <span class="n">the</span> <span class="n">two</span> <span class="n">top</span><span class="o">-</span><span class="n">most</span> <span class="n">available</span> <span class="n">bits</span> <span class="nb">set</span>
</pre></div>
</div>
<p>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.</p>
<p>The refcount for immortal objects will be set to <code class="docutils literal notranslate"><span class="pre">_Py_IMMORTAL_REFCNT</span></code>
(meaning the value will be halfway between <code class="docutils literal notranslate"><span class="pre">_Py_IMMORTAL_BIT</span></code> 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
<code class="docutils literal notranslate"><span class="pre">_Py_IMMORTAL_BIT</span></code>.</p>
<p>The difference means that an immortal object will still be considered
immortal, even if somehow its refcount were modified (e.g. by an older
stable ABI extension).</p>
<p>Note that top two bits of the refcount are already reserved for other
uses. Thats why we are using the third top-most bit.</p>
<p>The implementation is also open to using other values for the immortal
bit, such as the sign bit or 2^31 (for saturated refcounts on 64-bit).</p>
</section>
<section id="affected-api">
<h3><a class="toc-backref" href="#affected-api" role="doc-backlink">Affected API</a></h3>
<p>API that will now ignore immortal objects:</p>
<ul class="simple">
<li>(public) <code class="docutils literal notranslate"><span class="pre">Py_INCREF()</span></code></li>
<li>(public) <code class="docutils literal notranslate"><span class="pre">Py_DECREF()</span></code></li>
<li>(public) <code class="docutils literal notranslate"><span class="pre">Py_SET_REFCNT()</span></code></li>
<li>(private) <code class="docutils literal notranslate"><span class="pre">_Py_NewReference()</span></code></li>
</ul>
<p>API that exposes refcounts (unchanged but may now return large values):</p>
<ul class="simple">
<li>(public) <code class="docutils literal notranslate"><span class="pre">Py_REFCNT()</span></code></li>
<li>(public) <code class="docutils literal notranslate"><span class="pre">sys.getrefcount()</span></code></li>
</ul>
<p>(Note that <code class="docutils literal notranslate"><span class="pre">_Py_RefTotal</span></code>, and consequently <code class="docutils literal notranslate"><span class="pre">sys.gettotalrefcount()</span></code>,
will not be affected.)</p>
<p>TODO: clarify the status of <code class="docutils literal notranslate"><span class="pre">_Py_RefTotal</span></code>.</p>
<p>Also, immortal objects will not participate in GC.</p>
</section>
<section id="immortal-global-objects">
<h3><a class="toc-backref" href="#immortal-global-objects" role="doc-backlink">Immortal Global Objects</a></h3>
<p>All runtime-global (builtin) objects will be made immortal.
That includes the following:</p>
<ul class="simple">
<li>singletons (<code class="docutils literal notranslate"><span class="pre">None</span></code>, <code class="docutils literal notranslate"><span class="pre">True</span></code>, <code class="docutils literal notranslate"><span class="pre">False</span></code>, <code class="docutils literal notranslate"><span class="pre">Ellipsis</span></code>, <code class="docutils literal notranslate"><span class="pre">NotImplemented</span></code>)</li>
<li>all static types (e.g. <code class="docutils literal notranslate"><span class="pre">PyLong_Type</span></code>, <code class="docutils literal notranslate"><span class="pre">PyExc_Exception</span></code>)</li>
<li>all static objects in <code class="docutils literal notranslate"><span class="pre">_PyRuntimeState.global_objects</span></code> (e.g. identifiers,
small ints)</li>
</ul>
<p>The question of making the full objects actually immutable (e.g.
for per-interpreter GIL) is not in the scope of this PEP.</p>
</section>
<section id="object-cleanup">
<h3><a class="toc-backref" href="#object-cleanup" role="doc-backlink">Object Cleanup</a></h3>
<p>In order to clean up all immortal objects during runtime finalization,
we must keep track of them.</p>
<p>For GC objects (“containers”) well leverage the GCs permanent
generation by pushing all immortalized containers there. During
runtime shutdown, the strategy will be to first let the runtime try
to do its best effort of deallocating these instances normally. Most
of the module deallocation will now be handled by
<code class="docutils literal notranslate"><span class="pre">pylifecycle.c:finalize_modules()</span></code> where we clean up the remaining
modules as best as we can. It will change which modules are available
during <code class="docutils literal notranslate"><span class="pre">__del__</span></code>, but thats already explicitly undefined behavior
in the docs. Optionally, we could do some topological ordering
to guarantee that user modules will be deallocated first before
the stdlib modules. Finally, anything left over (if any) can be found
through the permanent generation GC list which we can clear
after <code class="docutils literal notranslate"><span class="pre">finalize_modules()</span></code> is done.</p>
<p>For non-container objects, the tracking approach will vary on a
case-by-case basis. In nearly every case, each such object is directly
accessible on the runtime state, e.g. in a <code class="docutils literal notranslate"><span class="pre">_PyRuntimeState</span></code> or
<code class="docutils literal notranslate"><span class="pre">PyInterpreterState</span></code> field. We may need to add a tracking mechanism
to the runtime state for a small number of objects.</p>
<p>None of the cleanup will have a significant effect on performance.</p>
</section>
<section id="performance-regression-mitigations">
<span id="mitigations"></span><h3><a class="toc-backref" href="#performance-regression-mitigations" role="doc-backlink">Performance Regression Mitigations</a></h3>
<p>In the interest of clarity, here are some of the ways we are going
to try to recover some of the <a class="reference internal" href="#performance">4% performance</a>
we lose with the naive implementation of immortal objects.</p>
<p>Note that none of this section is actually part of the proposal.</p>
<section id="at-the-end-of-runtime-init-mark-all-objects-as-immortal">
<h4><a class="toc-backref" href="#at-the-end-of-runtime-init-mark-all-objects-as-immortal" role="doc-backlink">at the end of runtime init, mark all objects as immortal</a></h4>
<p>We can apply the concept from
<a class="reference internal" href="#immortal-mutable-objects">Immortal Mutable Objects</a> in the pursuit of getting back some of
that 4% performance we lose with the naive implementation of immortal
objects. At the end of runtime init we can mark <em>all</em> objects as
immortal and avoid the extra cost in incref/decref. We only need
to worry about immutability with objects that we plan on sharing
between threads without a GIL.</p>
</section>
<section id="drop-unnecessary-hard-coded-refcount-operations">
<h4><a class="toc-backref" href="#drop-unnecessary-hard-coded-refcount-operations" role="doc-backlink">drop unnecessary hard-coded refcount operations</a></h4>
<p>Parts of the C-API interact specifically with objects that we know
to be immortal, like <code class="docutils literal notranslate"><span class="pre">Py_RETURN_NONE</span></code>. Such functions and macros
can be updated to drop any refcount operations.</p>
</section>
<section id="specialize-for-immortal-objects-in-the-eval-loop">
<h4><a class="toc-backref" href="#specialize-for-immortal-objects-in-the-eval-loop" role="doc-backlink">specialize for immortal objects in the eval loop</a></h4>
<p>There are opportunities to optimize operations in the eval loop
involving specific known immortal objects (e.g. <code class="docutils literal notranslate"><span class="pre">None</span></code>). The
general mechanism is described in <a class="pep reference internal" href="../pep-0659/" title="PEP 659 Specializing Adaptive Interpreter">PEP 659</a>. Also see <a class="reference external" href="https://mail.python.org/archives/list/python-dev&#64;python.org/message/JLHRTBJGKAENPNZURV4CIJSO6HI62BV3/">Pyston</a>.</p>
</section>
<section id="other-possibilities">
<h4><a class="toc-backref" href="#other-possibilities" role="doc-backlink">other possibilities</a></h4>
<ul class="simple">
<li>mark every interned string as immortal</li>
<li>mark the “interned” dict as immortal if shared else share all interned strings</li>
<li>(Larry,MAL) mark all constants unmarshalled for a module as immortal</li>
<li>(Larry,MAL) allocate (immutable) immortal objects in their own memory page(s)</li>
<li>saturated refcounts using the 32 least-significant bits</li>
</ul>
</section>
</section>
<section id="solutions-for-accidental-de-immortalization">
<h3><a class="toc-backref" href="#solutions-for-accidental-de-immortalization" role="doc-backlink">Solutions for Accidental De-Immortalization</a></h3>
<p>In the <a class="reference internal" href="#accidental-de-immortalizing">Accidental De-Immortalizing</a> section we outlined a possible
negative consequence of immortal objects. Here we look at some
of the options to deal with that.</p>
<p>Note that we enumerate solutions here to illustrate that satisfactory
options are available, rather than to dictate how the problem will
be solved.</p>
<p>Also note the following:</p>
<ul class="simple">
<li>this only matters in the 32-bit stable-ABI case</li>
<li>it only affects immortal objects</li>
<li>there are no user-defined immortal objects, only built-in types</li>
<li>most immortal objects will be statically allocated
(and thus already must fail if <code class="docutils literal notranslate"><span class="pre">tp_dealloc()</span></code> is called)</li>
<li>only a handful of immortal objects will be used often enough
to possibly face this problem in practice (e.g. <code class="docutils literal notranslate"><span class="pre">None</span></code>)</li>
<li>the main problem to solve is crashes coming from <code class="docutils literal notranslate"><span class="pre">tp_dealloc()</span></code></li>
</ul>
<p>One fundamental observation for a solution is that we can reset
an immortal objects refcount to <code class="docutils literal notranslate"><span class="pre">_Py_IMMORTAL_REFCNT</span></code>
when some condition is met.</p>
<p>With all that in mind, a simple, yet effective, solution would be
to reset an immortal objects refcount in <code class="docutils literal notranslate"><span class="pre">tp_dealloc()</span></code>.
<code class="docutils literal notranslate"><span class="pre">NoneType</span></code> and <code class="docutils literal notranslate"><span class="pre">bool</span></code> already have a <code class="docutils literal notranslate"><span class="pre">tp_dealloc()</span></code> that calls
<code class="docutils literal notranslate"><span class="pre">Py_FatalError()</span></code> if triggered. The same goes for other types based
on certain conditions, like <code class="docutils literal notranslate"><span class="pre">PyUnicodeObject</span></code> (depending on
<code class="docutils literal notranslate"><span class="pre">unicode_is_singleton()</span></code>), <code class="docutils literal notranslate"><span class="pre">PyTupleObject</span></code>, and <code class="docutils literal notranslate"><span class="pre">PyTypeObject</span></code>.
In fact, the same check is important for all statically declared object.
For those types, we would instead reset the refcount. For the
remaining cases we would introduce the check. In all cases,
the overhead of the check in <code class="docutils literal notranslate"><span class="pre">tp_dealloc()</span></code> should be too small
to matter.</p>
<p>Other (less practical) solutions:</p>
<ul class="simple">
<li>periodically reset the refcount for immortal objects</li>
<li>only do that for high-use objects</li>
<li>only do it if a stable-ABI extension has been imported</li>
<li>provide a runtime flag for disabling immortality</li>
</ul>
<p>(<a class="reference external" href="https://mail.python.org/archives/list/python-dev&#64;python.org/message/OXAYWH47ZGLOWXTNKCIW4YE5PXGHNT4Y/">The discussion thread</a>
has further detail.)</p>
<p>Regardless of the solution we end up with, we can do something else
later if necessary.</p>
<p>TODO: Add a note indicating that the implemented solution does not
affect the overall ~performance-neutral~ outcome.</p>
</section>
<section id="documentation">
<h3><a class="toc-backref" href="#documentation" role="doc-backlink">Documentation</a></h3>
<p>The immortal objects behavior and API are internal, implementation
details and will not be added to the documentation.</p>
<p>However, we will update the documentation to make public guarantees
about refcount behavior more clear. That includes, specifically:</p>
<ul class="simple">
<li><code class="docutils literal notranslate"><span class="pre">Py_INCREF()</span></code> - change “Increment the reference count for object o.”
to “Indicate taking a new reference to object o.”</li>
<li><code class="docutils literal notranslate"><span class="pre">Py_DECREF()</span></code> - change “Decrement the reference count for object o.”
to “Indicate no longer using a previously taken reference to object o.”</li>
<li>similar for <code class="docutils literal notranslate"><span class="pre">Py_XINCREF()</span></code>, <code class="docutils literal notranslate"><span class="pre">Py_XDECREF()</span></code>, <code class="docutils literal notranslate"><span class="pre">Py_NewRef()</span></code>,
<code class="docutils literal notranslate"><span class="pre">Py_XNewRef()</span></code>, <code class="docutils literal notranslate"><span class="pre">Py_Clear()</span></code></li>
<li><code class="docutils literal notranslate"><span class="pre">Py_REFCNT()</span></code> - 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.”</li>
<li><code class="docutils literal notranslate"><span class="pre">Py_SET_REFCNT()</span></code> - refer to <code class="docutils literal notranslate"><span class="pre">Py_REFCNT()</span></code> about how values over 1
may be substituted with some over value</li>
</ul>
<p>We <em>may</em> also add a note about immortal objects to the following,
to help reduce any surprise users may have with the change:</p>
<ul class="simple">
<li><code class="docutils literal notranslate"><span class="pre">Py_SET_REFCNT()</span></code> (a no-op for immortal objects)</li>
<li><code class="docutils literal notranslate"><span class="pre">Py_REFCNT()</span></code> (value may be surprisingly large)</li>
<li><code class="docutils literal notranslate"><span class="pre">sys.getrefcount()</span></code> (value may be surprisingly large)</li>
</ul>
<p>Other API that might benefit from such notes are currently undocumented.
We wouldnt add such a note anywhere else (including for <code class="docutils literal notranslate"><span class="pre">Py_INCREF()</span></code>
and <code class="docutils literal notranslate"><span class="pre">Py_DECREF()</span></code>) since the feature is otherwise transparent to users.</p>
</section>
</section>
<section id="reference-implementation">
<h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2>
<p>The implementation is proposed on GitHub:</p>
<p><a class="reference external" href="https://github.com/python/cpython/pull/19474">https://github.com/python/cpython/pull/19474</a></p>
</section>
<section id="open-issues">
<h2><a class="toc-backref" href="#open-issues" role="doc-backlink">Open Issues</a></h2>
<ul class="simple">
<li>how realistic is the <a class="reference internal" href="#accidental-de-immortalizing">Accidental De-Immortalizing</a> concern?</li>
</ul>
</section>
<section id="references">
<h2><a class="toc-backref" href="#references" role="doc-backlink">References</a></h2>
<section id="prior-art">
<h3><a class="toc-backref" href="#prior-art" role="doc-backlink">Prior Art</a></h3>
<ul class="simple">
<li><a class="reference external" href="https://mail.python.org/archives/list/python-dev&#64;python.org/message/JLHRTBJGKAENPNZURV4CIJSO6HI62BV3/">Pyston</a></li>
</ul>
</section>
<section id="discussions">
<h3><a class="toc-backref" href="#discussions" role="doc-backlink">Discussions</a></h3>
<p>This was discussed in December 2021 on python-dev:</p>
<ul class="simple">
<li><a class="reference external" href="https://mail.python.org/archives/list/python-dev&#64;python.org/thread/7O3FUA52QGTVDC6MDAV5WXKNFEDRK5D6/#TBTHSOI2XRWRO6WQOLUW3X7S5DUXFAOV">https://mail.python.org/archives/list/python-dev&#64;python.org/thread/7O3FUA52QGTVDC6MDAV5WXKNFEDRK5D6/#TBTHSOI2XRWRO6WQOLUW3X7S5DUXFAOV</a></li>
<li><a class="reference external" href="https://mail.python.org/archives/list/python-dev&#64;python.org/thread/PNLBJBNIQDMG2YYGPBCTGOKOAVXRBJWY">https://mail.python.org/archives/list/python-dev&#64;python.org/thread/PNLBJBNIQDMG2YYGPBCTGOKOAVXRBJWY</a></li>
</ul>
</section>
<section id="runtime-object-state">
<h3><a class="toc-backref" href="#runtime-object-state" role="doc-backlink">Runtime Object State</a></h3>
<p>Here is the internal state that the CPython runtime keeps
for each Python object:</p>
<ul class="simple">
<li><a class="reference external" href="https://github.com/python/cpython/blob/80a9ba537f1f1666a9e6c5eceef4683f86967a1f/Include/object.h#L107">PyObject.ob_refcnt</a>: the objects <a class="reference internal" href="#refcounting">refcount</a></li>
<li><a class="reference external" href="PyGC_Head">_PyGC_Head</a>: (optional) the objects node in a list of <a class="reference internal" href="#refcounting">“GC” objects</a></li>
<li><a class="reference external" href="PyObject_HEAD_EXTRA">_PyObject_HEAD_EXTRA</a>: (optional) the objects node in the list of heap objects</li>
</ul>
<p><code class="docutils literal notranslate"><span class="pre">ob_refcnt</span></code> is part of the memory allocated for every object.
However, <code class="docutils literal notranslate"><span class="pre">_PyObject_HEAD_EXTRA</span></code> is allocated only if CPython was built
with <code class="docutils literal notranslate"><span class="pre">Py_TRACE_REFS</span></code> defined. <code class="docutils literal notranslate"><span class="pre">PyGC_Head</span></code> is allocated only if the
objects type has <code class="docutils literal notranslate"><span class="pre">Py_TPFLAGS_HAVE_GC</span></code> set. Typically this is only
container types (e.g. <code class="docutils literal notranslate"><span class="pre">list</span></code>). Also note that <code class="docutils literal notranslate"><span class="pre">PyObject.ob_refcnt</span></code>
and <code class="docutils literal notranslate"><span class="pre">_PyObject_HEAD_EXTRA</span></code> are part of <code class="docutils literal notranslate"><span class="pre">PyObject_HEAD</span></code>.</p>
</section>
<section id="reference-counting-with-cyclic-garbage-collection">
<span id="refcounting"></span><h3><a class="toc-backref" href="#reference-counting-with-cyclic-garbage-collection" role="doc-backlink">Reference Counting, with Cyclic Garbage Collection</a></h3>
<p>Garbage collection is a memory management feature of some programming
languages. It means objects are cleaned up (e.g. memory freed)
once they are no longer used.</p>
<p>Refcounting is one approach to garbage collection. The language runtime
tracks how many references are held to an object. When code takes
ownership of a reference to an object or releases it, the runtime
is notified and it increments or decrements the refcount accordingly.
When the refcount reaches 0, the runtime cleans up the object.</p>
<p>With CPython, code must explicitly take or release references using
the C-APIs <code class="docutils literal notranslate"><span class="pre">Py_INCREF()</span></code> and <code class="docutils literal notranslate"><span class="pre">Py_DECREF()</span></code>. These macros happen
to directly modify the objects refcount (unfortunately, since that
causes ABI compatibility issues if we want to change our garbage
collection scheme). Also, when an object is cleaned up in CPython,
it also releases any references (and resources) it owns
(before its memory is freed).</p>
<p>Sometimes objects may be involved in reference cycles, e.g. where
object A holds a reference to object B and object B holds a reference
to object A. Consequently, neither object would ever be cleaned up
even if no other references were held (i.e. a memory leak). The
most common objects involved in cycles are containers.</p>
<p>CPython has dedicated machinery to deal with reference cycles, which
we call the “cyclic garbage collector”, or often just
“garbage collector” or “GC”. Dont let the name confuse you.
It only deals with breaking reference cycles.</p>
<p>See the docs for a more detailed explanation of refcounting
and cyclic garbage collection:</p>
<ul class="simple">
<li><a class="reference external" href="https://docs.python.org/3.11/c-api/intro.html#reference-counts">https://docs.python.org/3.11/c-api/intro.html#reference-counts</a></li>
<li><a class="reference external" href="https://docs.python.org/3.11/c-api/refcounting.html">https://docs.python.org/3.11/c-api/refcounting.html</a></li>
<li><a class="reference external" href="https://docs.python.org/3.11/c-api/typeobj.html#c.PyObject.ob_refcnt">https://docs.python.org/3.11/c-api/typeobj.html#c.PyObject.ob_refcnt</a></li>
<li><a class="reference external" href="https://docs.python.org/3.11/c-api/gcsupport.html">https://docs.python.org/3.11/c-api/gcsupport.html</a></li>
</ul>
</section>
</section>
<section id="copyright">
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
<p>This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.</p>
</section>
</section>
<hr class="docutils" />
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0683.rst">https://github.com/python/peps/blob/main/peps/pep-0683.rst</a></p>
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0683.rst">2024-06-12 18:00:45 GMT</a></p>
</article>
<nav id="pep-sidebar">
<h2>Contents</h2>
<ul>
<li><a class="reference internal" href="#pep-acceptance-conditions">PEP Acceptance Conditions</a></li>
<li><a class="reference internal" href="#abstract">Abstract</a><ul>
<li><a class="reference internal" href="#scope">Scope</a></li>
<li><a class="reference internal" href="#implementation-summary">Implementation Summary</a></li>
</ul>
</li>
<li><a class="reference internal" href="#motivation">Motivation</a><ul>
<li><a class="reference internal" href="#reducing-cpu-cache-invalidation">Reducing CPU Cache Invalidation</a></li>
<li><a class="reference internal" href="#avoiding-data-races">Avoiding Data Races</a></li>
<li><a class="reference internal" href="#avoiding-copy-on-write">Avoiding Copy-on-Write</a></li>
</ul>
</li>
<li><a class="reference internal" href="#rationale">Rationale</a></li>
<li><a class="reference internal" href="#impact">Impact</a><ul>
<li><a class="reference internal" href="#benefits">Benefits</a></li>
<li><a class="reference internal" href="#performance">Performance</a></li>
<li><a class="reference internal" href="#backward-compatibility">Backward Compatibility</a><ul>
<li><a class="reference internal" href="#accidental-immortality">Accidental Immortality</a></li>
<li><a class="reference internal" href="#stable-abi">Stable ABI</a></li>
<li><a class="reference internal" href="#accidental-de-immortalizing">Accidental De-Immortalizing</a></li>
</ul>
</li>
<li><a class="reference internal" href="#alternate-python-implementations">Alternate Python Implementations</a></li>
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
<li><a class="reference internal" href="#maintainability">Maintainability</a></li>
</ul>
</li>
<li><a class="reference internal" href="#specification">Specification</a><ul>
<li><a class="reference internal" href="#public-refcount-details">Public Refcount Details</a></li>
<li><a class="reference internal" href="#constraints">Constraints</a></li>
<li><a class="reference internal" href="#immortal-mutable-objects">Immortal Mutable Objects</a></li>
<li><a class="reference internal" href="#implicitly-immortal-objects">Implicitly Immortal Objects</a></li>
<li><a class="reference internal" href="#un-immortalizing-objects">Un-Immortalizing Objects</a></li>
<li><a class="reference internal" href="#py-immortal-refcnt">_Py_IMMORTAL_REFCNT</a></li>
<li><a class="reference internal" href="#affected-api">Affected API</a></li>
<li><a class="reference internal" href="#immortal-global-objects">Immortal Global Objects</a></li>
<li><a class="reference internal" href="#object-cleanup">Object Cleanup</a></li>
<li><a class="reference internal" href="#performance-regression-mitigations">Performance Regression Mitigations</a><ul>
<li><a class="reference internal" href="#at-the-end-of-runtime-init-mark-all-objects-as-immortal">at the end of runtime init, mark all objects as immortal</a></li>
<li><a class="reference internal" href="#drop-unnecessary-hard-coded-refcount-operations">drop unnecessary hard-coded refcount operations</a></li>
<li><a class="reference internal" href="#specialize-for-immortal-objects-in-the-eval-loop">specialize for immortal objects in the eval loop</a></li>
<li><a class="reference internal" href="#other-possibilities">other possibilities</a></li>
</ul>
</li>
<li><a class="reference internal" href="#solutions-for-accidental-de-immortalization">Solutions for Accidental De-Immortalization</a></li>
<li><a class="reference internal" href="#documentation">Documentation</a></li>
</ul>
</li>
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
<li><a class="reference internal" href="#open-issues">Open Issues</a></li>
<li><a class="reference internal" href="#references">References</a><ul>
<li><a class="reference internal" href="#prior-art">Prior Art</a></li>
<li><a class="reference internal" href="#discussions">Discussions</a></li>
<li><a class="reference internal" href="#runtime-object-state">Runtime Object State</a></li>
<li><a class="reference internal" href="#reference-counting-with-cyclic-garbage-collection">Reference Counting, with Cyclic Garbage Collection</a></li>
</ul>
</li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
<br>
<a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0683.rst">Page Source (GitHub)</a>
</nav>
</section>
<script src="../_static/colour_scheme.js"></script>
<script src="../_static/wrap_tables.js"></script>
<script src="../_static/sticky_banner.js"></script>
</body>
</html>