GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — bpo-44590: Lazily allocate frame objects by markshannon · Pull Request #27077 · python/cpython

Conversation

@markshannon

@markshannon markshannon commented Jul 9, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

This PR:

  1. Moves all essential frame data to the (per-thread) stack
  2. Lazily allocates heap frame objects only when needed.

For most calls to Python functions, we can get the necessary memory for a stack frame by just bumping a pointer.
When we do need a frame object, for tracebacks and the like, it is lazily allocated.

According to my benchmarking run this PR produces a maximum speedup of 31% with a mean of 7%!

The performance numbers are suspiciously good.
If someone could run the comparison on their machine that would be much appreciated.

https://bugs.python.org/issue44590

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Techcable, corona10, notatallshaw, Fidget-Spinner, sthagen, j178, bgruening, methane, bluss, johnthagen, and 6 more reacted with hooray emoji ayazhafiz and Dentrax reacted with rocket emoji
All reactions
  • 🎉 16 reactions
  • 🚀 2 reactions
markshannon added 30 commits June 21, 2021 18:50
@markshannon
Turn specials array into struct and add 'lasti' to it.
79aeaf6
@markshannon
Move stack-depth from frame object to specials struct.
3b6a4e8
@markshannon
Rename 'specials' to 'frame' as it now includes most of the data for …
6f0ba40
…a frame, except for the locals.
@markshannon
Refactor, pushing PyFrame upward toward _PyEval_Vector.
8543c27
@markshannon
Add pointer from stack frame to frame object and rename tstate.frame …
423d403
…to tstate.pyframe as preparation for lazily created frames.
@markshannon
More refactoring. Add tstate.frame for the top stack frame.
7bf6c30
@markshannon
Convert use of PyFrameObject to _PyFrame.
861a8d9
@markshannon
Replace most remaining uses frame object in the interpreter with stac…
35e793c
…k frame.
@markshannon
Convert more uses of frameobject to frame.
192094e
@markshannon
Move f_state from frame object to frame.
3f601a7
@markshannon
Compute f_back when on thread stack, only filling in value when frame…
bd95c32
… object outlives stack invocation.
@markshannon
Add NULL check
9961abc
@markshannon
Get lazy f_back working (it still leaks).
32af707
@markshannon
Use frames not frameobjects in sys._getframe()
ac7dbe8
@markshannon
NULL out frame->previous when leaving frame.
f33d291
@markshannon
Frames now include nlocalspuls, so they have valid layout after code …
5c23a36
…has been deleted.
@markshannon
Move ownership of frame in generator from frame object ot generator o…
910e991
…bject.
@markshannon
Remove localsptr field from frame object.
22e1c9b
@markshannon
Add new _PyEval_EvalNoFrame function for evaluating frames directly.
f84a3f0
@markshannon
Allow for lazily created frames.
1180a44
@markshannon
Do not create frame objects for Python calls.
c76de89
@markshannon
Don't create frame objects for generators.
15aeef1
@markshannon
Fix memory leak
1d2e1ce
@markshannon
Merge branch 'main' into lazy-frame-updated
1b19f8b
@markshannon
Restore support for PEP 523.
d619bae
@markshannon
Streamline pushing and popping stack frames a bit.
d147d03
@markshannon
Merge branch 'main' into lazy-frame
25c6a71
@markshannon
Add f_ prefix back to several frame fields to ease porting C code tha…
618b094
…t introspects frames.
@markshannon
Add NEWS
e5da338
@markshannon
Remove debugging artifact.
dda0b0c
pablogsal
pablogsal reviewed Jul 20, 2021
View reviewed changes
Comment thread Python/pystate.c Outdated

PyObject **
_PyThreadState_PushLocals(PyThreadState *tstate, int size)
_Py_NO_INLINE PyObject **

@pablogsal pablogsal Jul 20, 2021

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Why _Py_NO_INLINE?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@markshannon markshannon Jul 21, 2021

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

This is the slow path. We want to make sure the fast path is inlined.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
pablogsal
pablogsal reviewed Jul 20, 2021
View reviewed changes
Comment thread Python/pystate.c Outdated
PyObject **
_PyThreadState_PushLocals(PyThreadState *tstate, int size)
_Py_NO_INLINE PyObject **
_PyThreadState_PushChunk(PyThreadState *tstate, int size)

@pablogsal pablogsal Jul 20, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

If we are going to take pointers to arbitrary places in memory, doesn't the standard require that those must be aligned? How are we making sure that all pointers to this region are aligned?. From the standard:

A pointer to an object or incomplete type may be converted to a pointer to a different object or incomplete type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.

Many compilers normally assume aligned pointer access.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@markshannon markshannon Jul 21, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Why do you think the returned memory would be unaligned?
How is this any different from malloc?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@pablogsal pablogsal Jul 21, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Why do you think the returned memory would be unaligned?

My question is not if the returned memory would be unaligned, but if we are taking pointers to random places of the chunk array. For instance, if we are doing anything isomorphic to this:

        data = (char *)malloc(16);
        sp1 = (short*)(data+3);

then we are doing it wrong. I am asking not because I think we are doing it wrong, but because i am still wrapping around the structure you have in mind for the chunks, because there is 3 or 4 layers of indirections, a "chunk struct" and at least 3 pointer accesses when pushin frames, so immediately I am wondering if this mechanism is taking this correctly.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@pablogsal pablogsal Jul 21, 2021

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I compiled Python with the undefined behavior sanitizer and it doesn't complain, so seems that there is no problem here.

On contrast, my example shows:

example.c:1313:30: runtime error: load of misaligned address 0x55f1ba0cd283 for type 'short int', which requires 2 byte alignment
0x55f1ba0cd283: note: pointer points here
 00  3d 0e 0b e5 f4 55 00 00  00 00 00 00 00 00 00 00  09 09 49 42 4d 39 30 36  21 00 00 00 00 00 00

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@pablogsal pablogsal Jul 21, 2021

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

On the other hand it found this (unrelated to this PR):

home/pablogsal/github/python/main/Modules/_sha3/kcp/KeccakP-1600-opt64.c:467:9: runtime error: load of misaligned address 0x563579d3c3f7 for type 'UINT64', whic
h requires 8 byte alignment
0x563579d3c3f7: note: pointer points here
 23 23 23 23 23  23 23 23 23 23 23 23 23  23 23 23 23 23 23 23 23  23 23 23 23 23 23 23 23  23 23 23
             ^
/home/pablogsal/github/python/main/Modules/_sha3/kcp/KeccakP-1600-opt64.c:467:9: runtime error: load of misaligned address 0x563579d3c3ff for type 'UINT64', whic
h requires 8 byte alignment
0x563579d3c3ff: note: pointer points here
 23 23 23 23 23  23 23 23 23 23 23 23 23  23 23 23 23 23 23 23 23  23 23 23 23 23 23 23 23  23 23 23
             ^
/home/pablogsal/github/python/main/Modules/_sha3/kcp/KeccakP-1600-opt64.c:467:9: runtime error: load of misaligned address 0x563579d3c407 for type 'UINT64', whic
h requires 8 byte alignment
0x563579d3c407: note: pointer points here
 23 23 23 23 23  23 23 23 23 23 23 23 23  23 23 23 23 23 23 23 23  23 23 23 23 23 23 23 23  23 23 23
             ^
/home/pablogsal/github/python/main/Modules/_sha3/kcp/KeccakP-1600-opt64.c:467:9: runtime error: load of misaligned address 0x563579d3c40f for type 'UINT64', whic
h requires 8 byte alignment
0x563579d3c40f: note: pointer points here
 23 23 23 23 23  23 23 23 23 23 23 23 23  23 23 23 23 23 23 23 23  23 23 23 23 23 23 23 23  23 23 23
             ^

I will open an issue

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
pablogsal
pablogsal reviewed Jul 20, 2021
View reviewed changes
Comment thread Python/pystate.c
if (new == NULL) {
return NULL;
}
tstate->datastack_chunk->top = tstate->datastack_top - &tstate->datastack_chunk->data[0];

@pablogsal pablogsal Jul 20, 2021

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Suggested change
tstate->datastack_chunk->top = tstate->datastack_top - &tstate->datastack_chunk->data[0];
tstate->datastack_chunk->top = tstate->datastack_top - tstate->datastack_chunk->data;

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@pablogsal pablogsal Jul 20, 2021

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I am missing anything here?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@markshannon markshannon Jul 21, 2021

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

No. It's just a hint to the reader that data is an array within the chunk.
chunk->data looks like a pointer deference, but &chunk->data[0] hints that this is an address calculation.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
pablogsal
pablogsal reviewed Jul 20, 2021
View reviewed changes
Comment thread Python/pystate.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

markshannon added 2 commits July 21, 2021 12:15
@markshannon
Various small clarifications as suggested by Pablo.
90ed5b6
@markshannon
Refactor interpreter frame code into its own file. Improve a few names.
593a348
@markshannon
markshannon requested a review from a team as a code owner July 21, 2021 12:26
markshannon added 2 commits July 21, 2021 14:20
@markshannon
Tidy up assert.
b775f13
@markshannon
Fix warning on Windows.
e8476b2
pablogsal
pablogsal approved these changes Jul 22, 2021
View reviewed changes

@pablogsal pablogsal left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

LGTM Great job!

Let's run the buildbots and land!

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@bedevere-bot bedevere-bot added awaiting merge and removed awaiting core review labels Jul 22, 2021
@markshannon markshannon added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Jul 26, 2021
@bedevere-bot

bedevere-bot commented Jul 26, 2021

Copy link
Copy Markdown

🤖 New build scheduled with the buildbot fleet by @markshannon for commit e8476b2 🤖

If you want to schedule another build, you need to add the ":hammer: test-with-buildbots" label again.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Jul 26, 2021
@markshannon

markshannon commented Jul 26, 2021

Copy link
Copy Markdown
Member Author

The failures on buildbot/AMD64 Windows10 PR look like something is wrong with that machine.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@markshannon
markshannon merged commit ae0a2b7 into python:main Jul 26, 2021
@bedevere-bot bedevere-bot removed the awaiting merge label Jul 26, 2021
@ncoghlan

ncoghlan commented Aug 1, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

One consequence of this change is that the code is now quite confusing, since the term "frame" is used ambiguously to refer to both frame objects and interpreter frames (specifically, the f_frame field on frame objects is now an interpreter frame reference, while tb_frame on tracebacks is still a full frame object).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ncoghlan

ncoghlan commented Aug 1, 2021

Copy link
Copy Markdown
Contributor

The generator code uses gi_xframe rather than gi_frame to make the distinction a bit clearer, and I think doing that systematically (so there is consistent code level distinction between execution frames and introspection frames) would make the new code much easier to follow.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ncoghlan

ncoghlan commented Aug 1, 2021

Copy link
Copy Markdown
Contributor

I've posted a proposed internal API refactoring design here: https://bugs.python.org/issue44800

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ncoghlan

ncoghlan commented Aug 1, 2021

Copy link
Copy Markdown
Contributor

And the PR implementing that proposed refactoring is up at #27525

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@markshannon
markshannon deleted the lazy-frame branch September 15, 2021 11:51
merwok
merwok reviewed Oct 12, 2021
View reviewed changes
Comment thread Modules/_tracemalloc.c
#include "pycore_traceback.h"
#include "pycore_hashtable.h"
#include "frameobject.h" // PyFrame_GetBack()
#include <pycore_frame.h>

@merwok merwok Oct 12, 2021

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I’m curious why this one is included with <> when all the headers above use ""
(I don’t know much C note!)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

gst reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@brandtbucher brandtbucher mentioned this pull request Dec 15, 2021
C extensions can't swap out live frames in CPython 3.11 python-greenlet/greenlet#281
Closed
@markshannon markshannon mentioned this pull request Apr 21, 2022
Create frame objects lazily when needed #88756
Closed
@The-Compiler The-Compiler mentioned this pull request May 26, 2022
Python/pystate.c:2218: _PyThreadState_PopFrame: Assertion `tstate->datastack_top >= base' failed. #93252
Closed
@kumaraditya303 kumaraditya303 mentioned this pull request Jun 6, 2022
Add coverage benchmark python/pyperformance#213
Merged
@andersk andersk mentioned this pull request Jun 28, 2023
Segmentation fault in 3.11.4, 3.12.0b3; _PyInterpreterFrame ownership issue #106092
Closed
@jburgy jburgy mentioned this pull request Oct 24, 2025
Upgrade to 3.11 jburgy/skean#2
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@merwok merwok merwok left review comments
@Fidget-Spinner Fidget-Spinner Fidget-Spinner left review comments
@pablogsal pablogsal pablogsal approved these changes
@1st1 1st1 Awaiting requested review from 1st1
@gvanrossum gvanrossum Awaiting requested review from gvanrossum

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

8 participants

@markshannon @pablogsal @bedevere-bot @Fidget-Spinner @gvanrossum @ncoghlan @merwok @the-knights-who-say-ni
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.