From 6809bb110736845457f7259860e698c115151a43 Mon Sep 17 00:00:00 2001 From: Shovy <1826784860@qq.com> Date: Sun, 26 Jul 2026 18:03:30 +0800 Subject: [PATCH 1/3] feat: replace % with power-of-two bitwise AND(#5) --- include/spsc/ring_buffer.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/spsc/ring_buffer.hpp b/include/spsc/ring_buffer.hpp index 971c64b..e8f155b 100644 --- a/include/spsc/ring_buffer.hpp +++ b/include/spsc/ring_buffer.hpp @@ -10,8 +10,8 @@ template class RingBuffer{ public: explicit RingBuffer(size_t capacity) : capacity_(capacity) { - if (capacity_ == 0) { - throw std::invalid_argument("RingBuffer capacity must be > 0"); + if (capacity_ == 0 || (capacity_ & (capacity_ - 1)) != 0) { + throw std::invalid_argument("RingBuffer capacity must be > 0 and a power of two"); } buffer_ = new T[capacity_]; } @@ -25,7 +25,7 @@ class RingBuffer{ const size_t current_w = write_idx.load(std::memory_order_relaxed); const size_t current_r = read_idx.load(std::memory_order_acquire); if (current_w - current_r == capacity_) return false; - buffer_[current_w % capacity_] = item; + buffer_[current_w & (capacity_ - 1)] = item; write_idx.store(current_w + 1, std::memory_order_release); return true; } @@ -35,7 +35,7 @@ class RingBuffer{ const size_t current_r = read_idx.load(std::memory_order_relaxed); const size_t current_w = write_idx.load(std::memory_order_acquire); if (current_w == current_r) return false; - item = buffer_[current_r % capacity_]; + item = buffer_[current_r & (capacity_ - 1)]; read_idx.store(current_r + 1, std::memory_order_release); return true; } @@ -61,4 +61,4 @@ class RingBuffer{ alignas(std::hardware_destructive_interference_size) std::atomic read_idx{0}; }; -} // namespace shovy \ No newline at end of file +} // namespace shovy From cf327c15b6e8d9bd0fe37ad889e40a7fca785f13 Mon Sep 17 00:00:00 2001 From: Shovy <1826784860@qq.com> Date: Sun, 26 Jul 2026 18:03:55 +0800 Subject: [PATCH 2/3] docs: update benchnmark report with #5 --- docs/benchmark_report.md | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/benchmark_report.md b/docs/benchmark_report.md index 6a86c15..193d4d7 100644 --- a/docs/benchmark_report.md +++ b/docs/benchmark_report.md @@ -72,6 +72,33 @@ This clearly motivates the use of **weaker memory ordering** (`acquire`/`release | **1024** | ~6.43 ns | **155.60 M/s** | ±1.92 M/s (1.23%) | | **4096** | ~6.68 ns | **149.81 M/s** | ±4.28 M/s (2.86%) | +### 📈 SPSC RingBuffer Power-of-Two Bitwise AND (Issue #5) + +* **Memory Order**: `acquire`/`release` (same as #3) +* **Alignment**: `alignas(std::hardware_destructive_interference_size)` (same as #4) +* **Key Optimization**: Replaced modulo operation % capacity with bitwise AND & (capacity - 1), requiring capacity to be a power of two + + +| Capacity | Operation Latency (avg) | Throughput (ops/s) | 5-run Std. Dev. (CV) | +|:---------|:------------------------|:-------------------|:---------------------| +| **64** | ~12.34 ns | **81.038 M/s** | ±2.03 M/s (2.51%) | +| **1024** | ~5.34 ns | **187.27 M/s** | ±4.09 M/s (2.18%) | +| **4096** | ~5.87 ns | **170.23 M/s** | ±15.26 M/s (8.96%) | + +### 👀 Observation + +Significant throughput gains at large capacities: At capacity 1024, throughput jumps from 155.6 M/s (#4) to 187.27 M/s—a ~20.3% improvement. At capacity 4096, it improves from 149.8 M/s to 170.26 M/s (~13.6% gain). This clearly demonstrates the benefit of eliminating integer division (div instruction), which typically takes 20–30 CPU cycles, whereas & completes in a single cycle. + +Regression at small capacity (64): Throughput drops from 89.98 M/s to 81.03 M/s (~10% decrease). Possible explanations: + +- At tiny capacities, the buffer frequently hits full/empty states, where branch misprediction overhead dominates and masks the gain from bitwise operations. +- Poor cache prefetching at small buffer sizes may cause the CPU to stall while waiting for memory coherence, negating the benefit of faster arithmetic. +- The high CV (8.96%) at capacity 4096 indicates greater performance variability under large buffers, possibly due to system load or CPU dynamic frequency scaling. + +Engineering trade-off: This optimization requires capacity to be a power of two (otherwise & (capacity - 1) produces incorrect results). This is a classic space-for-time strategy—ideal for performance-critical scenarios where buffer sizes can be pre-aligned (e.g., network packet pools, memory pools). Applications requiring arbitrary prime capacities must retain the modulo operator. + + + --- ## 🔍 Analysis @@ -90,7 +117,8 @@ This clearly motivates the use of **weaker memory ordering** (`acquire`/`release - On the AMD Ryzen platform, false sharing may not be the dominant bottleneck because the L3 cache is shared and the CPU handles MESI protocol efficiently. - However, alignment remains a **defensive measure** that prevents unpredictable performance cliffs when the system is under heavy load. - --- -*Benchmark #1 ~ #4 executed on 2026‑07‑25. All results are reproducible using the provided Google Benchmark suite.* \ No newline at end of file +*Benchmark #1 ~ #4 executed on 2026‑07‑25.* +*Benchmark #5 executed on 2026‑07‑26.* +*All results are reproducible using the provided Google Benchmark suite.* \ No newline at end of file From 52b54abd75da5c68deff733edfa3e1cc092c8018 Mon Sep 17 00:00:00 2001 From: Shovy <1826784860@qq.com> Date: Sun, 26 Jul 2026 18:04:33 +0800 Subject: [PATCH 3/3] test: update bitwise test(#5) --- tests/ring_buffer_test.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/ring_buffer_test.cpp b/tests/ring_buffer_test.cpp index 79d5494..825ad53 100644 --- a/tests/ring_buffer_test.cpp +++ b/tests/ring_buffer_test.cpp @@ -35,6 +35,19 @@ TEST(RingBufferTest, BasicOperations){ EXPECT_TRUE(rb.full()); } +TEST(RingBufferTest, RejectNonPowerOfTwoCapacity){ + EXPECT_THROW( + shovy::RingBuffer buffer(100), + std::invalid_argument + ); +} + +TEST(RingBufferTest, AcceptPowerOfTwoCapacity){ + EXPECT_NO_THROW( + shovy::RingBuffer buffer(1024); + ); +} + TEST(RingBufferTest, MultiThreadDataRaceDemonstration){ const size_t count = 10'000'000; shovy::RingBuffer buffer(1024); @@ -71,4 +84,4 @@ TEST(RingBufferTest, MultiThreadDataRaceDemonstration){ } } -} \ No newline at end of file +}