1018 lines
72 KiB
HTML
1018 lines
72 KiB
HTML
|
|
|||
|
<!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> » </li>
|
|||
|
<li><a href="../pep-0000/">PEP Index</a> » </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 <ericsnowcurrently at gmail.com>, Eddie Elizondo <eduardo.elizondorueda at gmail.com></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@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@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@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 CPython’s 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
|
|||
|
Python’s 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 object’s refcount will never reach 0, and thus
|
|||
|
the object will never be cleaned up (except when the runtime knows
|
|||
|
it’s 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>Here’s a high-level look at the implementation:</p>
|
|||
|
<p>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. 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 object’s 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
|
|||
|
other’s 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.
|
|||
|
CPython’s GIL helps reduce this effect, since only one thread runs at a
|
|||
|
time, but it doesn’t 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” isn’t 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 proposal’s
|
|||
|
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@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 object’s 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 we’d 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@python.org/message/JLHRTBJGKAENPNZURV4CIJSO6HI62BV3/">Pyston</a> shouldn’t 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 incref’ed so much
|
|||
|
that it reaches the magic value needed to be considered immortal.
|
|||
|
That means it would never be decref’ed 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 wouldn’t
|
|||
|
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 decref’ed 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 don’t
|
|||
|
consider it a concern of this proposal.</p>
|
|||
|
<p>On builds with much smaller maximum refcounts, like 32-bit platforms,
|
|||
|
the consequences aren’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:</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 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.</p>
|
|||
|
<p>Between all of the above cases, the proposal doesn’t 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 object’s
|
|||
|
<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 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 <code class="docutils literal notranslate"><span class="pre">None</span></code> 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 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 isn’t 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 doesn’t 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 object’s 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 object’s
|
|||
|
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> -> <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> -> <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> -> <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> -> <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 don’t 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 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 <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 application’s 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 won’t 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 isn’t 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
|
|||
|
object’s 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 object’s 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 isn’t a change in behavior. The only difference is that the
|
|||
|
immortal object (holding the reference) doesn’t 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 we’d set it to the value that it
|
|||
|
would have been if it hadn’t 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. That’s 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”) we’ll leverage the GC’s 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 that’s 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@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 object’s 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 object’s 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@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 wouldn’t 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@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@python.org/thread/7O3FUA52QGTVDC6MDAV5WXKNFEDRK5D6/#TBTHSOI2XRWRO6WQOLUW3X7S5DUXFAOV">https://mail.python.org/archives/list/python-dev@python.org/thread/7O3FUA52QGTVDC6MDAV5WXKNFEDRK5D6/#TBTHSOI2XRWRO6WQOLUW3X7S5DUXFAOV</a></li>
|
|||
|
<li><a class="reference external" href="https://mail.python.org/archives/list/python-dev@python.org/thread/PNLBJBNIQDMG2YYGPBCTGOKOAVXRBJWY">https://mail.python.org/archives/list/python-dev@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 object’s <a class="reference internal" href="#refcounting">refcount</a></li>
|
|||
|
<li><a class="reference external" href="PyGC_Head">_PyGC_Head</a>: (optional) the object’s 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 object’s 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
|
|||
|
object’s 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-API’s <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 object’s 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 it’s 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”. Don’t 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>
|