{{ message }}
gh-130704: Strength reduce LOAD_FAST{_LOAD_FAST} - #130708
Merged
Merged
Conversation
Ref will be 2 if borrowed
Otherwise, it ends up being loaded using `LOAD_FAST_CHECK`, which increfs and causes the refcount check to fail when it uses `LOAD_FAST_BORROW`.
These need to be tagged appropriately, not just increfed, so that they are decrefed when the frame is destroyed.
This may be 1 if the `LOAD_FAST` is optimized to a `LOAD_FAST_BORROW`. It's not clear that this is testing anything useful, so remove it.
The initial value will differ depending on whether a owned or borrowed reference is loaded onto the operand stack.
These don't push enough values on the stack.
…unconditional_jump_threading` Make sure we have a statically known stack depth
…mized to borrowed variants
PyStackRef_AsPyObjectSteal creates a new reference if the stackref is deferred. This reference is leaked if we deopt before the corresponding decref.
These may provide support for borrowed references contained in frames closer to the top of the call stack. Add them to a list attached to the frame when they are overwritten, to be destroyed when the frame is destroyed.
`STORE_FAST_LOAD_FAST` and `LOAD_FAST_AND_CLEAR` both need to kill the local.
This ensures we hit all the blocks
Contributor
Author
markshannon
approved these changes
Mar 26, 2025
markshannon
left a comment
Member
There was a problem hiding this comment.
Missing one hint for the cases generator, otherwise looks good.
We could make the analysis more robust by using the cases generator, but that's for a later PR.
| _PyInterpreterFrame *gen_frame = &gen->gi_iframe; | ||
| STACK_SHRINK(1); | ||
| _PyFrame_StackPush(gen_frame, v); | ||
| _PyFrame_StackPush(gen_frame, PyStackRef_MakeHeapSafe(v)); |
Member
There was a problem hiding this comment.
I think you need a DEAD(v); here
| break; | ||
| } | ||
|
|
||
| // We treat opcodes that do not consume all of their inputs on |
Member
There was a problem hiding this comment.
This approach seems fine for now, but the code generator knows exactly how many values are popped and consumed, as opposed to peek at.
We should add a _PyOpcode_num_peeked function, the we'd have consumed = _PyOpcode_num_popped() - _PyOpcode_num_peeked() which would be more robust.
jamadden
added a commit
to python-greenlet/greenlet
that referenced
this pull request
Apr 11, 2025
- Header files have moved around. - Reference counting has changed. It appears to be python/cpython#130708 that's eliding some reference counting within functions and caused us to need to lower our expected reference count in a few places. NOTE: I'm not 100% sure this is the case; dis.dis is broken and won't show the function bodies so I can't confirm the new opcodes are being used.
jamadden
added a commit
to python-greenlet/greenlet
that referenced
this pull request
Apr 11, 2025
- Header files have moved around. - Reference counting has changed. It appears to be python/cpython#130708 that's eliding some reference counting within functions and caused us to need to lower our expected reference count in a few places. NOTE: I'm not 100% sure this is the case; dis.dis is broken and won't show the function bodies so I can't confirm the new opcodes are being used.
jamadden
added a commit
to python-greenlet/greenlet
that referenced
this pull request
Apr 11, 2025
- Header files have moved around. - Reference counting has changed. It appears to be python/cpython#130708 that's eliding some reference counting within functions and caused us to need to lower our expected reference count in a few places. NOTE: I'm not 100% sure this is the case; dis.dis is broken and won't show the function bodies so I can't confirm the new opcodes are being used.
jamadden
added a commit
to python-greenlet/greenlet
that referenced
this pull request
Apr 11, 2025
- Header files have moved around. - Reference counting has changed. It appears to be python/cpython#130708 that's eliding some reference counting within functions and caused us to need to lower our expected reference count in a few places. NOTE: I'm not 100% sure this is the case; but `dis.dis` shows the new opcode being used for the variables we're testing the refcount of.
3 tasks
navytux
added a commit
to navytux/pygolang
that referenced
this pull request
Mar 18, 2026
Running that test on py3.14 fails:
____________________ test_strings_refcount _____________________
def test_strings_refcount():
# buffer with string data - not bytes nor unicode so that when builtin
# string types are patched there is no case where bytes is created from the
# same bytes, or unicode is created from the same unicode - only increasing
# refcount of original object.
data = bytearray([ord('a'), ord('b'), ord('c'), ord('4')])
# first verify our logic on std type
obj = bytes(data); assert type(obj) is bytes
> gc.collect(); assert sys.getrefcount(obj) == 1+1 # +1 due to obj passed to getrefcount call
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: assert 1 == (1 + 1)
E + where 1 = <built-in function getrefcount>(b'abc4')
E + where <built-in function getrefcount> = sys.getrefcount
golang/golang_str_test.py:301: AssertionError
That happens because on py3.14 the interpreter now does not add a
reference when passing on-stack argument to a function if it can prove
that is safe to do so.
Please see python/cpython#130708 and
python/cpython@f2379535 for details.
-> Adjust the test to account for that on py3.14 .
/reviewed-by @jerome
/reviewed-on https://lab.nexedi.com/nexedi/pygolang/-/merge_requests/39 + https://lab.nexedi.com/nexedi/slapos/-/merge_requests/1863#note_260064
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.

This PR eliminates most reference counting overhead for references pushed onto the operand stack using
LOAD_FAST{_LOAD_FAST}when we can be sure that the reference in the frame outlives the reference that is pushed onto the operand stack. Instructions that meet this criteria are replaced with new variants (LOAD_FAST_BORROW{_LOAD_FAST_BORROW}) that push appropriately tagged borrowed references.Performance on the benchmark suite looks good:
This approach looks like its quite effective at optimization too, at least on the benchmark suite. Roughly 97% of
LOAD_FAST{_LOAD_FAST}instructions are optimized according to pystats. Note that these stats were collected using fastbench, so may not match those collected usingpyperformanceexactly.The main pieces of the PR are:
New bytecodes
This adds two new bytecode instructions:
LOAD_FAST_BORROWand its superinstruction form,LOAD_FAST_BORROW_LOAD_FAST_BORROW.A new optimization pass
This adds a new optimization pass,
optimize_load_fast, to the bytecode compiler that identifies and optimizes eligible instructions. Please read the detailed comment inflowgraph.cfor a description of how it works.Runtime support changes
A new function,
PyStackRef_Borrow, was added to the stackref API. It creates a new stackref from an existing stackref without incrementing the reference count on the underlying object.There are a few places in the runtime where we need to convert borrowed references into owned references:
RETURN_VALUEorYIELD_VALUE).f_locals. We place the old reference into a tuple owned by the frame object.The default build also required:
PyFloat_FromDoubleConsumeInputs.LOAD_FASTvariants #130704📚 Documentation preview 📚: https://cpython-previews--130708.org.readthedocs.build/