Python 3.11 specializes ordinary interpreter operations
Python 3.11 improves execution by specializing common operations and reducing frame overhead. Its design retains dynamic behavior while making stable execution patterns cheaper.
Most Python operations must support more possibilities than a particular call site actually uses. An attribute access can involve several kinds of object and lookup behavior, yet a loop may repeatedly access the same attribute on the same kind of instance. Python 3.11 uses that local stability to select cheaper interpreter operations while retaining a route back to general behavior.
The October 24 stable release brings the Faster CPython work into a final interpreter. PEP 659 describes the central specialization mechanism. It operates at the granularity of individual interpreter instructions, so an unsuccessful assumption can be discarded locally without reconstructing the state of a large optimized region.
I choose Python because its ecosystem often supplies the difficult implementation a problem needs. Faster execution of ordinary Python code is useful on its own terms, even when much of that ecosystem already performs expensive work in native libraries. The distinction between those workloads matters when interpreting the release's performance claims.
Specialization makes a local assumption
A generic instruction handles a range of inputs. The adaptive interpreter observes the types and values that occur at a particular instruction and can replace it with a specialized version. That version takes a fast path when its assumptions hold. It still has to preserve the operation's Python meaning.
PEP 659 calls the instruction-replacement process quickening. Adaptive instructions keep counters and periodically attempt specialization. A successful specialization replaces the operation with an instruction suited to the observed case. This avoids treating every execution as a fresh search through all the possibilities that the general implementation supports.
The specialization is speculative. Inputs can change because Python remains dynamic, and an instruction cannot simply keep applying an invalid assumption. The proposal describes counters that track whether the specialized case continues to apply. A mismatch performs the generic operation, and enough misses return the instruction to its adaptive form.
This is why the size of the optimized region matters. A single instruction has a small, explicit boundary. De-optimization can replace its opcode without needing to undo a large region's partially executed assumptions. The design spends effort making adaptation inexpensive enough to use early in execution.
The 3.11.0 what's new document explains that specialization targets hot code. It avoids spending the same effort on operations executed only once. The interpreter can retry specialization as usage changes, so an initial pattern does not permanently determine an instruction's implementation.
Cached information must preserve current values
Attribute lookup illustrates the range of specialized cases. PEP 659 describes separate possibilities for instance values, module attributes and slots. Each case avoids work that the general attribute operation must perform to determine what kind of lookup applies. The specialization still depends on the assumptions that make that direct path valid.
Global lookup makes another distinction visible. The proposal describes caching an index into a namespace after checking the relevant keys. This does not require freezing every value in the namespace. A value can change while the arrangement of keys remains suitable for indexed access.
For built-in lookup, the guards also need to account for the global namespace. A name that previously resolved through builtins can become a global. The specialization therefore has to preserve the language's lookup order, not merely remember the object it found last time. A cached route to a value differs from treating that value as immutable.
These examples explain why an interpreter can exploit stable structure without changing the language into a statically typed one. The optimized operation retains checks for the conditions it relies on. General behavior remains available when those conditions no longer describe the current execution.
The release also combines common instruction pairs into superinstructions. That reduces dispatch overhead between operations. It is a separate contribution from a faster implementation of attribute lookup or arithmetic, although the mechanisms can work together during quickening.
Inline caches affect the bytecode representation
Specializations often need more information than an instruction operand can hold. PEP 659 places that information in entries immediately following the instruction. The release documentation describes these entries as CACHE instructions and says opcodes expect a specific number of them.
That arrangement has consequences for bytecode tools. Populated caches can resemble arbitrary instructions when read without understanding the layout. A tool that treats every entry as executable bytecode can misinterpret adaptive data. Reading or modifying raw quickened bytecode therefore requires knowledge of the cache layout.
The memory cost follows from storing extra information. PEP 659 compares the cache needs with the older interpreter's existing caches and distinguishes cold code from heavily executed code. The result depends on how much code becomes hot, rather than on a single fixed increase for every application.
Other 3.11 changes offset some costs. The release document describes lazily created object namespaces and greater sharing of dictionary keys. It also describes smaller execution structures. These changes make a whole-interpreter memory claim more complicated than counting the new cache entries alone.
Function calls do less bookkeeping
Python 3.11 separates ordinary execution frames from the old-style frame objects used by debuggers and introspection. The release documentation says those objects are now created only when requested. Most user code therefore does not pay for a full frame object on every Python function call.
That change reduces work even when a program is not dominated by one highly specialized operation. Calling Python functions is common enough that reducing frame creation affects many code paths. The release attributes a measured contribution to that work, but its practical mechanism is the avoided allocation and reduced bookkeeping.
Calls between Python functions also avoid recursively invoking the C evaluation function in the ordinary case. The interpreter prepares another frame and continues evaluating within it. The documentation says most such calls consume no C stack space. Python's recursion limit remains a separate policy, so this is not permission to assume unlimited recursion.
These implementation changes affect extensions that inspect private frame structures. The final release's porting guidance replaces direct access with supported functions. An optimization can preserve Python-level behavior while changing a representation that native tools had come to depend on.
I favor that separation between execution needs and inspection needs. Debuggers still need meaningful frames, but ordinary calls need not allocate every object that an inspection tool might eventually request. Access functions give the interpreter a place to create or update the required representation when it becomes necessary.
Performance numbers describe particular measurements
The download announcement reports a 1.22-times average speedup on the standard benchmark suite, while the version-specific what's new document reports 1.25 times. These are published summaries of benchmark results, not a guarantee for an arbitrary service. The documentation identifies GCC builds on Ubuntu Linux for its average and describes substantial variation among individual benchmarks.
The same document explains why I/O-heavy programs and programs already doing most computation in C extensions may see little benefit. Interpreter optimization reduces interpreter work. It cannot remove the time spent waiting for external operations or automatically accelerate a native numerical kernel that already performs the computation outside Python execution.
The distinction also applies to startup. Python 3.11 freezes core startup modules into statically allocated code objects. That avoids reading cached bytecode, unmarshalling it and allocating a code object for those modules. Short-running commands can benefit from that work even when they do not execute a long-lived hot loop.
These mechanisms explain different parts of the performance result. Startup avoids repeated preparation of core modules. Function-call changes reduce bookkeeping, and adaptive instructions reduce repeated general-purpose dispatch within hot code. Their contributions depend on which costs a particular program actually incurs.
Failure reporting becomes more precise too
The release changes the evidence available when code fails. PEP 657's source-column information lets tracebacks identify an expression within a line. A line with several attribute accesses or nested calls can now show which part triggered the exception. That is useful independently of the interpreter's execution speed.
PEP 654 also adds exception groups and except*, enabling a program to raise and handle multiple unrelated failures together. This supports abstractions such as task groups whose children can fail independently. The error model can represent what happened without forcing the first observed exception to stand for every failure.
These are complementary changes for systems built with Python. Faster ordinary execution reduces runtime overhead, while richer failure information exposes more of the operation that failed. The final interpreter supplies both without requiring an application to adopt every new language construct. Existing code can receive the execution improvements, and tools can use the new position and exception interfaces where their reporting model needs them.