93. Immortal Objects
PEP 683 immortal objects: _Py_IMMORTAL_REFCNT sentinel, zero-cost incref/decref, and implications for forks.
93. Immortal Objects
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 overhead
This 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 appear
Immortal 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 decremented
This model applied to nearly every object in the runtime:
integers
strings
lists
dicts
functions
modules
types
singletons
Even 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 = None
This may appear trivial, but internally:
load reference to None
increment refcount
store pointer
later decrement refcount
Now imagine millions of threads repeatedly touching shared objects:
None
True
False
small integers
interned strings
builtin types
These objects become synchronization hotspots.
Under free-threaded execution:
multiple CPU cores modify same refcount field
cache lines bounce between cores
atomic operations serialize updates
Reference counting traffic alone can dominate execution cost.
93.3 The Core Observation
The key observation behind immortal objects is:
many runtime objects effectively never die
Examples:
None
True
False
0
1
2
""
()
int
str
list
dict
object
type
These objects typically survive until interpreter shutdown.
Therefore:
tracking exact reference counts provides little value
The 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 deallocated
Conceptually:
normal object
refcount changes dynamically
immortal object
refcount treated as permanent
Instead 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 coordination
especially 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 pattern
The runtime recognizes:
this object should never reach zero
Therefore:
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 destroyed
They are ideal immortality candidates.
93.7 Why Small Integers Matter
Small integers are touched constantly.
Example:
for i in range(1000000):
x = i + 1
Even simple loops repeatedly reference:
0
1
2
small counters
temporary arithmetic values
Without immortality:
refcount increments occur constantly
Under free-threaded execution:
atomic refcount traffic becomes enormous
Immortal 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 state
Without immortality:
global refcount contention appears repeatedly
Making 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 entirely
This 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 count
Instead:
refcount acts as immortal sentinel state
The 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 zero
Immortality 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 forever
Immortality 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 transitions
However:
immortal objects can still reference ordinary objects
ordinary objects can reference immortal objects
The 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 execute
This is acceptable because immortal objects are intentionally permanent.
Examples:
None
True
False
do 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 checks
The runtime can safely assume such objects persist indefinitely.
93.16 Fast Identity Checks
Python identity checks:
x is None
depend on pointer identity.
Immortal objects strengthen assumptions around these patterns:
the object definitely survives
pointer remains valid
no destruction races occur
This 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 object
These 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 metadata
all heavily use interned strings.
93.18 Immortality and Type Objects
Builtin type objects are heavily shared.
Example:
int
str
dict
list
These are accessed constantly:
instance creation
attribute lookup
type checking
slot dispatch
Without immortality:
type object refcounts become synchronization hotspots
Immortal builtin types reduce runtime contention significantly.
93.19 ABI Compatibility
One important design goal is ABI stability.
Existing extensions assume:
PyObject contains ob_refcnt
Immortal objects preserve this structure.
The meaning changes slightly:
some refcounts no longer represent exact ownership totals
but 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 disappear
This 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 constantly
This creates coherence traffic:
core A modifies cache line
core B invalidates it
core A reloads it
Immortality 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 count
With immortality:
some objects never change counts meaningfully
Tools inspecting reference counts must understand:
immortal sentinel values
special lifetime rules
nonstandard refcount semantics
Developers 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 None
still behaves identically.
The difference is internal:
less synchronization
fewer refcount updates
better multicore scalability
The 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 management
Future runtime work may extend:
thread-local ownership
deferred refcount merging
regional allocation
object pinning
runtime specialization
Immortality 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 metadata
By eliminating unnecessary synchronization on heavily shared objects, immortal objects improve scalability, reduce cache contention, and support parallel interpreter execution.