PEP 683 immortal objects: _Py_IMMORTAL_REFCNT sentinel, zero-cost incref/decref, and implications for forks.
Immortal objects are objects whose lifetime is treated as effectively permanent by the CPython runtime. Their reference counts no longer behave like ordinary objects, and the interpreter avoids many normal reference counting operations on them.
Immortal objects were introduced as part of larger runtime optimization work, especially in support of free-threaded CPython and multicore scalability.
The main idea is simple:
some objects exist for the entire interpreter lifetime
incrementing and decrementing their reference counts is unnecessary
skipping refcount updates reduces synchronization overheadThis chapter examines:
why immortal objects exist
how CPython traditionally managed object lifetime
why reference counting becomes expensive
how immortality works internally
which objects become immortal
how the object header changes semantically
how immortal objects interact with free-threading
what risks and tradeoffs appearImmortal objects change one of the oldest assumptions in CPython: that every object participates uniformly in reference counting.
93.1 Traditional Reference Counting
Historically, every CPython object maintained a mutable reference count.
Conceptually:
typedef struct {
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;Every new strong reference increased the count:
Py_INCREF(obj);Every released reference decreased the count:
Py_DECREF(obj);When the count reached zero:
object destructor executes
memory is released
contained references are decrementedThis model applied to nearly every object in the runtime:
integers
strings
lists
dicts
functions
modules
types
singletonsEven globally shared objects participated fully in refcount operations.
93.2 Why Reference Counting Becomes Expensive
Reference counting is simple conceptually, but expensive at scale.
Each increment and decrement requires memory writes:
++obj->ob_refcnt;
--obj->ob_refcnt;In traditional single-threaded execution, this overhead was acceptable.
Under multicore execution, the cost grows dramatically.
Consider:
x = NoneThis may appear trivial, but internally:
load reference to None
increment refcount
store pointer
later decrement refcountNow imagine millions of threads repeatedly touching shared objects:
None
True
False
small integers
interned strings
builtin typesThese objects become synchronization hotspots.
Under free-threaded execution:
multiple CPU cores modify same refcount field
cache lines bounce between cores
atomic operations serialize updatesReference counting traffic alone can dominate execution cost.
93.3 The Core Observation
The key observation behind immortal objects is:
many runtime objects effectively never dieExamples:
None
True
False
0
1
2
""
()
int
str
list
dict
object
typeThese objects typically survive until interpreter shutdown.
Therefore:
tracking exact reference counts provides little valueThe runtime does not meaningfully benefit from repeatedly incrementing and decrementing them.
93.4 The Immortal Object Idea
Immortal objects use a special reference count state indicating:
this object should never be deallocatedConceptually:
normal object
refcount changes dynamically
immortal object
refcount treated as permanentInstead of:
Py_INCREF(Py_None);
Py_DECREF(Py_None);the runtime may skip updates entirely.
This reduces:
atomic synchronization
cache contention
memory writes
cross-core coordinationespecially in highly parallel execution.
93.5 Immortal Reference Counts
Immortality is represented through special reference count values.
Conceptually:
very large sentinel refcount
or
special immortal bit patternThe runtime recognizes:
this object should never reach zeroTherefore:
Py_INCREF(obj);can become:
if (!immortal(obj)) {
increment_refcount(obj);
}Similarly:
Py_DECREF(obj);may skip decrement logic entirely.
The object effectively exits normal lifetime accounting.
93.6 Which Objects Become Immortal
Immortality is most useful for heavily shared runtime objects.
Typical candidates:
| Category | Examples |
|---|---|
| Singletons | None, True, False |
| Small integers | -5 through cached positive integers |
| Interned strings | Frequently reused identifiers |
| Builtin types | int, str, list, dict |
| Static runtime objects | Global runtime metadata |
| Permanent constants | Empty tuple, empty bytes |
These objects are:
globally shared
frequently referenced
rarely or never destroyedThey are ideal immortality candidates.
93.7 Why Small Integers Matter
Small integers are touched constantly.
Example:
for i in range(1000000):
x = i + 1Even simple loops repeatedly reference:
0
1
2
small counters
temporary arithmetic valuesWithout immortality:
refcount increments occur constantlyUnder free-threaded execution:
atomic refcount traffic becomes enormousImmortal small integers significantly reduce synchronization pressure.
93.8 The Empty Tuple Optimization
The empty tuple is another important case.
Example:
()Many runtime operations reuse the same empty tuple object:
function defaults
empty argument tuples
internal APIs
temporary stateWithout immortality:
global refcount contention appears repeatedlyMaking the empty tuple immortal eliminates unnecessary updates.
93.9 Immortality and Free-Threaded CPython
Immortal objects are especially important in free-threaded CPython.
Traditional refcount updates:
++obj->ob_refcnt;become unsafe under parallel execution.
Free-threaded CPython therefore uses atomic operations:
atomic_fetch_add(...);Atomic operations are expensive.
Immortal objects reduce this cost dramatically:
shared singleton objects avoid atomic traffic entirelyThis improves scalability across multiple CPU cores.
Without immortality, free-threaded reference counting overhead would be substantially worse.
93.10 Object Headers Still Exist
Immortal objects still use normal object structures.
Conceptually:
typedef struct {
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;The difference is semantic:
refcount no longer represents actual ownership countInstead:
refcount acts as immortal sentinel stateThe object header format largely remains compatible.
This is important for ABI stability.
93.11 CPython Still Uses Reference Counting
Immortal objects do not replace reference counting entirely.
Most objects remain ordinary reference-counted objects:
x = [1, 2, 3]This list still behaves normally:
increment references
decrement references
destroy when refcount reaches zeroImmortality is selective.
The runtime applies it only where beneficial.
93.12 Why Not Make Everything Immortal
Making all objects immortal would create severe problems.
Example:
while True:
xs = [1, 2, 3]If lists never died:
memory usage would grow foreverImmortality works only for objects whose lifetime is already effectively permanent.
The runtime therefore distinguishes:
| Object Type | Lifetime |
|---|---|
| Global singleton | Permanent |
| Temporary container | Dynamic |
| User object | Dynamic |
| Runtime metadata | Often permanent |
| Frame object | Dynamic |
Most objects must still be reclaimed normally.
93.13 Interaction With the Garbage Collector
Immortal objects interact differently with cyclic GC.
Because they are never destroyed:
their lifetime no longer depends on refcount transitionsHowever:
immortal objects can still reference ordinary objects
ordinary objects can reference immortal objectsThe garbage collector must still reason about graph reachability correctly.
Immortality does not eliminate GC logic.
It mainly removes unnecessary destruction tracking for permanent objects.
93.14 Destructors and Finalization
Immortal objects never reach zero references.
Therefore:
their destructors never executeThis is acceptable because immortal objects are intentionally permanent.
Examples:
None
True
Falsedo not require cleanup logic.
Objects needing deterministic destruction are unsuitable for immortality.
93.15 Stable Object Addresses
Immortal objects also tend to have stable addresses.
Example:
id(None)typically remains stable throughout interpreter execution.
This benefits:
caches
interning systems
runtime metadata
fast-path comparisons
pointer identity checksThe runtime can safely assume such objects persist indefinitely.
93.16 Fast Identity Checks
Python identity checks:
x is Nonedepend on pointer identity.
Immortal objects strengthen assumptions around these patterns:
the object definitely survives
pointer remains valid
no destruction races occurThis becomes especially valuable under free-threaded execution.
93.17 Interned Strings and Immortality
Interned strings are another important case.
Python frequently reuses identifiers:
"name"
"value"
"append"
"dict"Interning reduces duplication:
multiple references share same string objectThese strings often persist for the interpreter lifetime.
Immortality removes unnecessary refcount traffic for such shared identifiers.
This matters because:
attribute lookup
dictionary keys
module globals
compiler metadataall heavily use interned strings.
93.18 Immortality and Type Objects
Builtin type objects are heavily shared.
Example:
int
str
dict
listThese are accessed constantly:
instance creation
attribute lookup
type checking
slot dispatchWithout immortality:
type object refcounts become synchronization hotspotsImmortal builtin types reduce runtime contention significantly.
93.19 ABI Compatibility
One important design goal is ABI stability.
Existing extensions assume:
PyObject contains ob_refcntImmortal objects preserve this structure.
The meaning changes slightly:
some refcounts no longer represent exact ownership totalsbut binary compatibility largely survives.
This is critical because the CPython ecosystem contains enormous amounts of native extension code.
93.20 Borrowed References Become Safer
Immortal objects indirectly improve safety for borrowed references.
Example:
PyObject *x = Py_None;Under immortality:
object definitely survives
destruction races disappearThis reduces certain lifetime hazards in concurrent execution.
However, immortality does not solve general borrowed-reference problems for mutable objects.
93.21 Cache Coherence Effects
Immortality improves hardware-level scalability.
Without immortality:
multiple cores update same refcount field
cache line invalidation occurs constantlyThis creates coherence traffic:
core A modifies cache line
core B invalidates it
core A reloads itImmortality removes many such writes entirely.
The effect can substantially improve multicore scaling.
93.22 Performance Implications
Immortal objects improve:
| Area | Benefit |
|---|---|
| Free-threaded scalability | Less atomic contention |
| Cache locality | Fewer invalidations |
| Singleton access | Faster |
| Small integer handling | Lower overhead |
| Type object access | Reduced synchronization |
| Interned string reuse | Lower traffic |
However, immortality also introduces costs:
| Cost | Reason |
|---|---|
| More runtime complexity | Special refcount states |
| More conditional logic | Skip checks |
| Less conceptual uniformity | Not all objects behave equally |
| Potential debugging confusion | Refcounts become nonliteral |
The runtime trades conceptual simplicity for scalability.
93.23 Debugging Immortal Objects
Immortal objects complicate debugging.
Historically:
refcount roughly represented ownership countWith immortality:
some objects never change counts meaningfullyTools inspecting reference counts must understand:
immortal sentinel values
special lifetime rules
nonstandard refcount semanticsDevelopers can no longer assume every object participates identically in memory management.
93.24 Immortality as a Runtime Optimization Layer
Immortality is fundamentally an optimization layer.
Python semantics remain unchanged:
None is Nonestill behaves identically.
The difference is internal:
less synchronization
fewer refcount updates
better multicore scalabilityThe optimization primarily exists to support modern runtime performance goals.
93.25 Future Directions
Immortal objects are part of a broader runtime evolution:
free-threaded execution
reduced global synchronization
better cache locality
scalable object lifetime managementFuture runtime work may extend:
thread-local ownership
deferred refcount merging
regional allocation
object pinning
runtime specializationImmortality is one step toward a more scalable interpreter architecture.
93.26 Chapter Summary
Immortal objects are objects whose lifetime is treated as permanent by the CPython runtime. Their reference counts no longer behave like ordinary ownership counters, and many refcount operations can be skipped entirely.
This optimization is especially important for free-threaded CPython, where atomic reference counting becomes expensive under multicore execution.
Immortal objects typically include:
singletons
small integers
builtin types
interned strings
empty containers
permanent runtime metadataBy eliminating unnecessary synchronization on heavily shared objects, immortal objects improve scalability, reduce cache contention, and support parallel interpreter execution.