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 — Optimize fast_locals and atomic ordering by youknowone · Pull Request #7289 · RustPython/RustPython

Conversation

@youknowone

@youknowone youknowone commented Mar 1, 2026 •
edited by coderabbitai Bot
Loading

Uh oh!

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

Copy link
Copy Markdown
Member

Summary by CodeRabbit

Release Notes

  • Performance Improvements
    • Optimized synchronization and atomic operations for enhanced runtime efficiency
    • Implemented lock-free storage for local variables
    • Streamlined execution path when tracing is disabled

Sorry, something went wrong.

Uh oh!

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

All reactions
youknowone added 3 commits March 1, 2026 15:42
@youknowone
Relax RefCount atomic ordering from SeqCst to Arc pattern
6895ddb
- inc/inc_by/get: SeqCst → Relaxed
- safe_inc CAS: SeqCst → Relaxed + compare_exchange_weak
- dec: SeqCst → Release + Acquire fence when count drops to 0
- leak CAS: SeqCst → AcqRel/Relaxed + compare_exchange_weak
@youknowone
Reuse existing Vec via prepend_arg in execute_call
89703b6
Replace vec![self_val] + extend(args.args) with
FuncArgs::prepend_arg() to avoid a second heap allocation
on every method call.
@youknowone
Skip downcast_ref checks in invoke when tracing is disabled
6a9be9f
Early return in PyCallable::invoke() when use_tracing is false,
avoiding two downcast_ref type checks on every function call.
@coderabbitai

coderabbitai Bot commented Mar 1, 2026 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR introduces lock-free frame locals storage via a new FastLocals type using UnsafeCell, replaces mutex-based access patterns with unsafe borrows across call sites, optimizes atomic operation orderings in RefCount to use Relaxed semantics, and adds a fast-path for disabled tracing in callable invocation.

Changes

Cohort / File(s) Summary
Atomic Ordering Optimizations
crates/common/src/refcount.rs
Replaces SeqCst with Relaxed ordering across RefCount methods; adds Release and Acquire/AcqRel fence in deallocation path; switches to compare_exchange_weak variants for reduced synchronization overhead.
Lock-Free Frame Locals Storage
crates/vm/src/frame.rs
Introduces new FastLocals struct wrapping UnsafeCell to replace PyMutex-based locals. Adds Send/Sync impls (with threading), borrow()/borrow_mut() methods, and Traverse impl. Updates Frame and ExecutingFrame field types accordingly.
FastLocals Access Migration
crates/vm/src/builtins/frame.rs, crates/vm/src/builtins/function.rs, crates/vm/src/builtins/super.rs
Replaces mutex .lock() calls with unsafe borrow()/borrow_mut() borrows for fastlocals access; includes safety comments indicating frame state assumptions.
Tracing Fast-Path
crates/vm/src/protocol/callable.rs
Adds short-circuit return in PyCallable::invoke when tracing is disabled, bypassing multi-branch tracing logic for direct call invocation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • more algorithm-independent GC infra #7194: Directly modifies the same refcount.rs file with identical atomic ordering changes across get/inc/dec/leak methods.
  • Clear frame locals and stack on generator close + Add dir_fd support for rmdir, remove/unlink, scandir #7222: Overlaps on frame/locals storage refactoring in crates/vm/src/frame.rs and related builtin call sites; potential for conflicts in FrameState and local access semantics.
  • Update bdb from v3.14.3 and fix trace_event #7255: Touches the same tracing call path in crates/vm/src/protocol/callable.rs, modifying trace event propagation semantics.

Poem

🐰 Hops through memory with careful grace,
Unsafe borrows replace the locks' embrace,
Relaxed atoms dance with less delay,
FastLocals stores frames in a lock-free way,
And when tracing sleeps, the short-paths play! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Optimize fast_locals and atomic ordering' directly and concisely summarizes the primary changes: replacing mutex-based fastlocals with a lock-free FastLocals type and optimizing atomic operation orderings from SeqCst to Relaxed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
  • X
  • Mastodon
  • Reddit
  • LinkedIn

Comment @coderabbitai help to get the list of available commands and usage tips.

All reactions

Sorry, something went wrong.

Uh oh!

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

@github-actions

github-actions Bot commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

Code has been automatically formatted

The code in this PR has been formatted using:

  • cargo fmt --all
    Please pull the latest changes before pushing again:
git pull origin worktree-perf-step1
All reactions

Sorry, something went wrong.

Uh oh!

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

@youknowone
Replace fastlocals PyMutex with UnsafeCell-based FastLocals
958853f
Eliminate per-instruction mutex lock/unlock overhead for local
variable access. FastLocals uses UnsafeCell with safety guaranteed
by the frame's state mutex and sequential same-thread execution.

Affects 14+ lock() call sites in hot instruction paths (LoadFast,
StoreFast, DeleteFast, and their paired variants).
@youknowone
youknowone force-pushed the worktree-perf-step1 branch from 2a5b8ce to 958853f Compare March 1, 2026 09:26
@github-actions
Auto-format: cargo fmt --all
516ff0f
@youknowone
youknowone marked this pull request as ready for review March 1, 2026 11:38
coderabbitai[bot]
coderabbitai Bot reviewed Mar 1, 2026
View reviewed changes

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/common/src/refcount.rs (1)

124-137: ⚠️ Potential issue | 🟠 Major

Use Acquire on successful safe_inc CAS for weak→strong upgrade synchronization.

The safe_inc method is used in weak reference upgrade paths (e.g., PyWeak::upgrade at line 688 in crates/vm/src/object/core.rs). Currently, line 136 uses Ordering::Relaxed on successful CAS, which provides no synchronization guarantees.

When upgrading from weak to strong, the CAS success must acquire visibility of memory written by previous strong reference holders. The dec() method uses Ordering::Release (line 149), establishing a release-acquire synchronization point. The successful safe_inc must use Ordering::Acquire to properly pair with this Release and ensure visibility of modifications made under the previous strong reference.

Proposed patch
             match self.state.compare_exchange_weak(
                 old.as_raw(),
                 new_state.as_raw(),
-                Ordering::Relaxed,
+                Ordering::Acquire,
                 Ordering::Relaxed,
             ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/common/src/refcount.rs` around lines 124 - 137, The CAS in safe_inc
(the compare_exchange_weak call that uses old.as_raw/new_state.as_raw) currently
uses Ordering::Relaxed for the success case; change the success ordering to
Ordering::Acquire while keeping the failure ordering relaxed, so the weak→strong
upgrade acquires the release from dec() and sees prior writes. Locate safe_inc,
the State::from_raw / add_strong usage and adjust the compare_exchange_weak
success ordering to Acquire.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/vm/src/frame.rs`:
- Around line 96-133: The FastLocals UnsafeCell can be accessed concurrently via
the Python-exposed f_locals() path (which calls locals()), violating the
assumption that access is serialized by with_exec(); to fix, either (A) remove
the unsafe impls unsafe impl Send/Sync for FastLocals and add runtime assertions
to prevent cross-thread access, or (B) gate all fastlocals borrows behind the
frame execution guard/token (have f_locals() acquire the same with_exec() guard
or a borrow token before calling locals()/FastLocals::borrow/borrow_mut), or (C)
add a runtime thread-check that panics on unsynchronized cross-thread access;
update the FastLocals docs to reflect the chosen model and adjust callers
(f_locals, locals, and any direct FastLocals usage) to obtain the guard/token or
to compile without Send/Sync accordingly.

---

Outside diff comments:
In `@crates/common/src/refcount.rs`:
- Around line 124-137: The CAS in safe_inc (the compare_exchange_weak call that
uses old.as_raw/new_state.as_raw) currently uses Ordering::Relaxed for the
success case; change the success ordering to Ordering::Acquire while keeping the
failure ordering relaxed, so the weak→strong upgrade acquires the release from
dec() and sees prior writes. Locate safe_inc, the State::from_raw / add_strong
usage and adjust the compare_exchange_weak success ordering to Acquire.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae8c0b3 and 516ff0f.

📒 Files selected for processing (6)
  • crates/common/src/refcount.rs
  • crates/vm/src/builtins/frame.rs
  • crates/vm/src/builtins/function.rs
  • crates/vm/src/builtins/super.rs
  • crates/vm/src/frame.rs
  • crates/vm/src/protocol/callable.rs

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread crates/vm/src/frame.rs
Comment on lines +96 to +133
/// Lock-free storage for local variables (localsplus).
///
/// # Safety
/// Access is serialized by the frame's state mutex in `with_exec()`, which
/// prevents concurrent frame execution. Trace callbacks that access `f_locals`
/// run sequentially on the same thread as instruction execution.
pub struct FastLocals {
inner: UnsafeCell<Box<[Option<PyObjectRef>]>>,
}

// SAFETY: Frame execution is serialized by the state mutex.
#[cfg(feature = "threading")]
unsafe impl Send for FastLocals {}
#[cfg(feature = "threading")]
unsafe impl Sync for FastLocals {}

impl FastLocals {
fn new(data: Box<[Option<PyObjectRef>]>) -> Self {
Self {
inner: UnsafeCell::new(data),
}
}

/// # Safety
/// Caller must ensure exclusive access (frame state locked or frame
/// not executing).
#[inline(always)]
pub unsafe fn borrow(&self) -> &[Option<PyObjectRef>] {
unsafe { &*self.inner.get() }
}

/// # Safety
/// Caller must ensure exclusive mutable access.
#[inline(always)]
#[allow(clippy::mut_from_ref)]
pub unsafe fn borrow_mut(&self) -> &mut [Option<PyObjectRef>] {
unsafe { &mut *self.inner.get() }
}

@coderabbitai coderabbitai Bot Mar 1, 2026

Copy link
Copy Markdown
Contributor

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Enumerate all fastlocals unsafe borrow call sites"
rg -n 'fastlocals\.borrow(_mut)?\(' \
  crates/vm/src/frame.rs \
  crates/vm/src/builtins/frame.rs \
  crates/vm/src/builtins/function.rs \
  crates/vm/src/builtins/super.rs

echo
echo "2) Inspect synchronization context around main externally reachable paths"
rg -n -A25 -B8 'fn locals\(|fn clear\(|fn f_locals\(|fn with_exec\(' \
  crates/vm/src/frame.rs \
  crates/vm/src/builtins/frame.rs

echo
echo "3) Inspect owner synchronization primitives (load/store vs CAS)"
rg -n 'owner\.(load|store|compare_exchange|compare_exchange_weak)' \
  crates/vm/src/frame.rs \
  crates/vm/src/builtins/frame.rs

Repository: RustPython/RustPython

Length of output: 9197


FastLocals synchronization contract is violated by externally reachable paths; f_locals() getter permits concurrent unsynchronized access.

The doc comment claims access is "serialized by the frame's state mutex in with_exec()", but the f_locals() getter (line 456 in builtins/frame.rs) directly calls locals() which accesses fastlocals without holding that guard (line 395 in frame.rs). The comment at lines 405–406 acknowledges fastlocals is intentionally accessed outside with_exec() to avoid blocking. Since f_locals() is Python-exposed and callable from any thread, and FastLocals is marked Send + Sync, concurrent calls from multiple threads will perform unsynchronized reads/writes to the same UnsafeCell, violating Rust's aliasing guarantees.

Gate fastlocals borrows behind a guard/token tied to frame execution, or document that frames must not be accessed concurrently and remove Send + Sync (or add runtime checks that panic if accessed cross-thread).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/frame.rs` around lines 96 - 133, The FastLocals UnsafeCell can
be accessed concurrently via the Python-exposed f_locals() path (which calls
locals()), violating the assumption that access is serialized by with_exec(); to
fix, either (A) remove the unsafe impls unsafe impl Send/Sync for FastLocals and
add runtime assertions to prevent cross-thread access, or (B) gate all
fastlocals borrows behind the frame execution guard/token (have f_locals()
acquire the same with_exec() guard or a borrow token before calling
locals()/FastLocals::borrow/borrow_mut), or (C) add a runtime thread-check that
panics on unsynchronized cross-thread access; update the FastLocals docs to
reflect the chosen model and adjust callers (f_locals, locals, and any direct
FastLocals usage) to obtain the guard/token or to compile without Send/Sync
accordingly.

Sorry, something went wrong.

Uh oh!

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

All reactions
@youknowone
youknowone merged commit 2b08445 into RustPython:main Mar 1, 2026
21 of 24 checks passed

Uh oh!

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

@youknowone
youknowone deleted the worktree-perf-step1 branch March 1, 2026 11:53
This was referenced Mar 1, 2026
Optimize unpack, str.__add__ and fastlocals #7293
Merged
Remove Frame mutex and use DataStack bump allocator for LocalsPlus #7333
Merged
Remove cells_frees duplicate storage from Frame #7351
Closed
Extract InterpreterFrame from Frame with Deref wrapper #7353
Merged
Fix thread-safety in GC, type cache, and instruction cache #7355
Merged
youknowone added a commit to youknowone/RustPython that referenced this pull request Mar 22, 2026
@youknowone @github-actions
Optimize fast_locals and atomic ordering (RustPython#7289)
0f0a5e5
* Relax RefCount atomic ordering from SeqCst to Arc pattern

- inc/inc_by/get: SeqCst → Relaxed
- safe_inc CAS: SeqCst → Relaxed + compare_exchange_weak
- dec: SeqCst → Release + Acquire fence when count drops to 0
- leak CAS: SeqCst → AcqRel/Relaxed + compare_exchange_weak

* Reuse existing Vec via prepend_arg in execute_call

Replace vec![self_val] + extend(args.args) with
FuncArgs::prepend_arg() to avoid a second heap allocation
on every method call.

* Skip downcast_ref checks in invoke when tracing is disabled

Early return in PyCallable::invoke() when use_tracing is false,
avoiding two downcast_ref type checks on every function call.

* Replace fastlocals PyMutex with UnsafeCell-based FastLocals

Eliminate per-instruction mutex lock/unlock overhead for local
variable access. FastLocals uses UnsafeCell with safety guaranteed
by the frame's state mutex and sequential same-thread execution.

Affects 14+ lock() call sites in hot instruction paths (LoadFast,
StoreFast, DeleteFast, and their paired variants).

* Auto-format: cargo fmt --all

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
youknowone added a commit that referenced this pull request Sep 16, 2026
@youknowone @github-actions
Optimize fast_locals and atomic ordering (#7289)
e339604
* Relax RefCount atomic ordering from SeqCst to Arc pattern

- inc/inc_by/get: SeqCst → Relaxed
- safe_inc CAS: SeqCst → Relaxed + compare_exchange_weak
- dec: SeqCst → Release + Acquire fence when count drops to 0
- leak CAS: SeqCst → AcqRel/Relaxed + compare_exchange_weak

* Reuse existing Vec via prepend_arg in execute_call

Replace vec![self_val] + extend(args.args) with
FuncArgs::prepend_arg() to avoid a second heap allocation
on every method call.

* Skip downcast_ref checks in invoke when tracing is disabled

Early return in PyCallable::invoke() when use_tracing is false,
avoiding two downcast_ref type checks on every function call.

* Replace fastlocals PyMutex with UnsafeCell-based FastLocals

Eliminate per-instruction mutex lock/unlock overhead for local
variable access. FastLocals uses UnsafeCell with safety guaranteed
by the frame's state mutex and sequential same-thread execution.

Affects 14+ lock() call sites in hot instruction paths (LoadFast,
StoreFast, DeleteFast, and their paired variants).

* Auto-format: cargo fmt --all

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@coderabbitai coderabbitai[bot] coderabbitai[bot] left review comments

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.

1 participant

@youknowone
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 01402520 — 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.