Skip to content

PEP 831: Frame pointers#4896

Open
pablogsal wants to merge 20 commits intopython:mainfrom
pablogsal:frame-pointers
Open

PEP 831: Frame pointers#4896
pablogsal wants to merge 20 commits intopython:mainfrom
pablogsal:frame-pointers

Conversation

@pablogsal
Copy link
Copy Markdown
Member

@pablogsal pablogsal commented Apr 9, 2026

Basic requirements (all PEP Types)

  • Read and followed PEP 1 & PEP 12
  • File created from the latest PEP template
  • PEP has next available number, & set in filename (pep-NNNN.rst), PR title (PEP 123: <Title of PEP>) and PEP header
  • Title clearly, accurately and concisely describes the content in 79 characters or less
  • Core dev/PEP editor listed as Author or Sponsor, and formally confirmed their approval
  • Author, Status (Draft), Type and Created headers filled out correctly
  • PEP-Delegate, Topic, Requires and Replaces headers completed if appropriate
  • Required sections included
    • Abstract (first section)
    • Copyright (last section; exact wording from template required)
  • Code is well-formatted (PEP 7/PEP 8) and is in code blocks, with the right lexer names if non-Python
  • PEP builds with no warnings, pre-commit checks pass and content displays as intended in the rendered HTML
  • Authors/sponsor added to .github/CODEOWNERS for the PEP

Standards Track requirements

  • PEP topic discussed in a suitable venue with general agreement that a PEP is appropriate
  • Suggested sections included (unless not applicable)
    • Motivation
    • Specification
    • Rationale
    • Backwards Compatibility
    • Security Implications
    • How to Teach This
    • Reference Implementation
    • Rejected Ideas
    • Open Issues
    • Acknowledgements
    • Footnotes
    • Change History
  • Python-Version set to valid (pre-beta) future Python version, if relevant
  • Any project stated in the PEP as supporting/endorsing/benefiting from the PEP formally confirmed such
  • Right before or after initial merging, PEP discussion thread created and linked to in Discussions-To and Post-History

📚 Documentation preview 📚: https://pep-previews--4896.org.readthedocs.build/pep-0831/

@pablogsal pablogsal requested review from a team and brettcannon as code owners April 9, 2026 22:01
@pablogsal pablogsal changed the title Frame pointers PEP 830: Frame pointers Apr 9, 2026
@brianschubert brianschubert added the new-pep A new draft PEP submitted for initial review label Apr 9, 2026
Copy link
Copy Markdown
Member

@savannahostrowski savannahostrowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the numbers we've seen, we should update 0.5-2%...if we're rounding up?

@pablogsal pablogsal changed the title PEP 830: Frame pointers PEP 831: Frame pointers Apr 9, 2026

Because the flags are in ``CFLAGS``, they propagate automatically to consumers
that build against CPython's reported compiler flags, such as C extensions
built via ``pip``, ``setuptools``, or direct ``sysconfig`` queries. Those
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I don't think pip directly builds any C extension, build backends do that

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
built via ``pip``, ``setuptools``, or direct ``sysconfig`` queries. Those
built via pip, Setuptools, or direct ``sysconfig`` queries. Those

Copy link
Copy Markdown
Member

@hugovk hugovk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting suggestions.


For all graphs below, the green dots are geometric means of the
individual benchmark's median, while orange lines are the median of our data points.
Hollow circles reperesent outliers.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Hollow circles reperesent outliers.
Hollow circles represent outliers.

https://github.com/Fidget-Spinner/python-framepointer-bench

.. [#missing_benchmarks] Some benchmarks are missing due to incompatibilities with
Python 3.15alpha.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Python 3.15alpha.
Python 3.15 alpha.

Comment on lines +325 to +332
CPython's own documentation already states the recommended fix::

For best results, Python should be compiled with
CFLAGS='-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer'
as this allows profilers to unwind using only the frame pointer
and not on DWARF debug information.

-- docs.python.org/3/howto/perf_profiling.html
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix link, no code formatting for quoting text:

Suggested change
CPython's own documentation already states the recommended fix::
For best results, Python should be compiled with
CFLAGS='-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer'
as this allows profilers to unwind using only the frame pointer
and not on DWARF debug information.
-- docs.python.org/3/howto/perf_profiling.html
:ref:`CPython's own documentation <python:perf_profiling>`__ already states the
recommended fix:
For best results, Python should be compiled with
``CFLAGS="-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"``
as this allows profilers to unwind using only the frame pointer
and not on DWARF debug information.

Comment on lines +362 to +370
-mno-omit-leaf-frame-pointer``. However, Ubuntu 24.04 LTS explicitly exempted
CPython::

In cases where the impact is high (such as the Python
interpreter), we'll continue to omit frame pointers until
this is addressed.

-- ubuntu.com/blog/ubuntu-performance-engineering-with-
frame-pointers-by-default
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
-mno-omit-leaf-frame-pointer``. However, Ubuntu 24.04 LTS explicitly exempted
CPython::
In cases where the impact is high (such as the Python
interpreter), we'll continue to omit frame pointers until
this is addressed.
-- ubuntu.com/blog/ubuntu-performance-engineering-with-
frame-pointers-by-default
-mno-omit-leaf-frame-pointer``. However, Ubuntu 24.04 LTS
`explicitly exempted CPython
<https://ubuntu.com/blog/ubuntu-performance-engineering-with-frame-pointers-by-default>`__:
In cases where the impact is high (such as the Python
interpreter), we'll continue to omit frame pointers until
this is addressed.

===================================== =======================
Apple M2 Mac Mini (arm64) 1.006x slower
macOS M3 Pro (arm64) 1.001x slower
Raspberry Pi (aarch64). 1.002x slower
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Raspberry Pi (aarch64). 1.002x slower
Raspberry Pi (aarch64) 1.002x slower

extension builds. If only the interpreter has frame pointers but extensions do
not, the chain is still broken at every C extension boundary. By adding the
flags to ``CFLAGS`` as reported by ``sysconfig``, extension builds that consume
CPython's compiler flags (for example via ``pip install``, ``setuptools``, or
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
CPython's compiler flags (for example via ``pip install``, ``setuptools``, or
CPython's compiler flags (for example via ``pip install``, Setuptools, or

the ``python`` binary, ``libpython``, and built-in extension modules under
``Modules/``.
2. The flags **are** written into the ``sysconfig`` data, so that third-party C
extensions built against this Python (via ``pip``, ``setuptools``, or direct
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
extensions built against this Python (via ``pip``, ``setuptools``, or direct
extensions built against this Python (via pip, Setuptools, or direct


Because the flags are in ``CFLAGS``, they propagate automatically to consumers
that build against CPython's reported compiler flags, such as C extensions
built via ``pip``, ``setuptools``, or direct ``sysconfig`` queries. Those
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
built via ``pip``, ``setuptools``, or direct ``sysconfig`` queries. Those
built via pip, Setuptools, or direct ``sysconfig`` queries. Those

Copy link
Copy Markdown
Member

@markshannon markshannon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very compelling.

I'd added a few quick comments on the first part of the PEP.

This is long, and might be a bit much to expect anyone to read through in one go.
Maybe some of the supporting analyses could be moved into appendices and linked to from the body of the PEP?

For example, the detailed, but long, analyses of perf and ebpf tools could be moved to appendices.

with frame pointers by default.** This PEP recommends that *every* compiled
component that participates in the Python call stack (C extensions, Rust
extensions, embedding applications, and native libraries) should enable
frame pointers. A frame-pointer chain is only as complete as its weakest
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
frame pointers. A frame-pointer chain is only as complete as its weakest
frame pointers. A frame-pointer chain is only as strong as its weakest

above) prevents these tools from producing useful call stacks for Python
processes, and undermines the perf trampoline support CPython shipped in 3.12.

The measured overhead is under 2% geometric mean for typical workloads.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what platform, build configuration, etc, etc?
Can you link to the data, otherwise the readers (at least this reader's) first thought is "where's the evidence?"

==========

Python's observability story (profiling, debugging, and system-level tracing)
is fundamentally limited by the absence of frame pointers. The core motivation
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't quite true. It isn't impossible without frame pointers, just much, much harder.

The performance wins that profiling enables far outweigh the modest overhead of
frame pointers. As Brendan Gregg notes: "I've seen frame pointers help find
performance wins ranging from 5% to 500%" [#gregg2024]_. A 0.5-2% overhead that
unlocks the ability to find 5-500% improvements is a favourable trade.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it clear, that large speedups are not going to happen for CPython, but it might happen for larger systems using Python as a glue language.

frame also stores the *previous* frame pointer, creating a linked list through
the entire call stack::

┌──────────────────┐
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have the arrows actually point to the previous frame. I think it is OK to use an image,instead of ASCII art.

Stack unwinding is the process of walking this chain to reconstruct the call
stack. Profilers do it to find out where the program is spending time;
debuggers do it to show backtraces; crash handlers do it to produce useful
error reports. With frame pointers, unwinding is a simple pointer chase: read
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
error reports. With frame pointers, unwinding is a simple pointer chase: read
error reports. With frame pointers, unwinding is a simply following pointers: read

I don't know if you can expect the readership to known the term "pointer chasing"

stack. Profilers do it to find out where the program is spending time;
debuggers do it to show backtraces; crash handlers do it to produce useful
error reports. With frame pointers, unwinding is a simple pointer chase: read
``%rbp``, follow the link, repeat. It takes microseconds and requires no
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
``%rbp``, follow the link, repeat. It takes microseconds and requires no
``%rbp``, follow the link, repeat. It is very fast and requires no

How long it actually takes depends on many factors.

default [#gcc_fomit]_. This frees the ``%rbp`` register for general use,
giving the optimiser one more register to work with. On x86-64 this is a gain
of one register out of 16 (about 7%). The performance benefit is small
(typically 0.5-2%) but it was considered worthwhile when the convention was
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, you need to be more nuanced about how much things slow down, and for which platforms.

I'd use terms like "a few" rather than 0.5-2 and add a link to a section with a more detailed breakdown

established for 32-bit x86, where the gain was one register out of 6 (~20%).

Without frame pointers, the linked list does not exist. Tools that need to
walk the call stack must instead parse DWARF debug information (a complex,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget Windows. It would make your case more compelling, as tools would need to parse DWARF and understand what Windows does.

follows the chain, and reconstructs the full call stack in microseconds. This
is fast enough to sample at 10,000 Hz in production with negligible overhead.

Without frame pointers, the profiler must fall back to DWARF unwinding, a
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pablogsal pablogsal requested a review from CAM-Gerlach as a code owner April 10, 2026 18:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new-pep A new draft PEP submitted for initial review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants