-
Notifications
You must be signed in to change notification settings - Fork 501
SpinLockMutex small cleanup; improved and extended benchmark #3706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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.
Pull Request Overview
This PR refactors the SpinLockMutex implementation and its benchmark suite to improve code clarity and performance testing. The changes simplify the locking logic and expand benchmark coverage to include std::mutex for performance comparison.
Key changes:
- Simplified
try_lock()by removing redundantload()check before exchange operation - Refactored
lock()to usetry_lock()internally for cleaner code - Fixed erroneous yield logic in
BM_ThreadYieldSpinLockThrashingbenchmark - Added
std::mutexbenchmark for performance comparison across different thread counts
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| api/include/opentelemetry/common/spin_lock_mutex.h | Simplified try_lock() and lock() implementations by removing redundant checks and restructuring loop logic |
| api/test/common/spinlock_benchmark.cc | Refactored benchmark template to use lambdas with captures, removed static qualifiers, fixed yield bug, and added std::mutex benchmark |
| while (!try_lock()) | ||
| { | ||
| // Try once | ||
| if (!flag_.exchange(true, std::memory_order_acquire)) | ||
| { | ||
| return; | ||
| } | ||
| // Spin-Fast (goal ~10ns) | ||
| for (std::size_t i = 0; i < SPINLOCK_FAST_ITERATIONS; ++i) | ||
| { |
Copilot
AI
Oct 20, 2025
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.
The spin-fast loop at line 101 is now unreachable. The while (!try_lock()) loop continuously retries try_lock() without ever entering the body to execute the spin-fast logic. The original structure with explicit if (!flag_.exchange(...)) return; allowed falling through to the spin logic when the lock failed. This breaks the intended backoff behavior.
| std::atomic_flag mutex{}; | ||
| #else | ||
| std::atomic_flag mutex = ATOMIC_FLAG_INIT; | ||
| alignas(8) std::atomic_flag mutex = ATOMIC_FLAG_INIT; |
Copilot
AI
Oct 20, 2025
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.
[nitpick] The alignas(8) specifier appears to be added without explanation. If alignment is needed for performance, consider adding a comment explaining why 8-byte alignment is chosen, or remove it if it was added unintentionally.
| alignas(8) std::atomic_flag mutex = ATOMIC_FLAG_INIT; | |
| std::atomic_flag mutex = ATOMIC_FLAG_INIT; |
| { | ||
| for (;;) | ||
| // Try once | ||
| while (!try_lock()) |
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 seems less efficient under contention. Calling try_lock() in every iteration causes repeated atomic exchanges and cache invalidations. The previous version avoided that by doing the initial exchange outside the spin loop.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3706 +/- ##
==========================================
+ Coverage 89.90% 89.95% +0.06%
==========================================
Files 225 225
Lines 7281 7101 -180
==========================================
- Hits 6545 6387 -158
+ Misses 736 714 -22
🚀 New features to boost your workflow:
|
| { | ||
| return !flag_.load(std::memory_order_relaxed) && | ||
| !flag_.exchange(true, std::memory_order_acquire); | ||
| return !flag_.exchange(true, std::memory_order_acquire); |
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.
How is this more efficient? load(std::memory_order_relaxed) would avoid having a memory fence and wait for cache sync. Having said that the whole idea of SpinLock in user mode seems dubious especially in high contention scenarios.
|
I'm confused why this CI/Format check fails! Any hint is welcome |
|
Tip to format all the code (C++, CMake, Bazel): See https://github.com/open-telemetry/cpp-build-tools/tree/main/cpp_format_tools |
Fixes # (issue)
Changes
load()not required intry_lock()lock()BM_ThreadYieldSpinLockThrashingstd::mutexto benchmarkstaticas code is already in unnamed namespaceIt seems that at least on Apple M1 (lClang+libc) the std::mutex outperforms SpinlockMutex with higher number of threads.