自旋屏障代码在AMD 7950X不同CPU核心运行的性能差异疑问
自旋屏障性能随CPU核心选择差异的原因分析
问题背景
分析自旋屏障代码时,发现其运行性能因CPU核心选择不同存在显著差异:
- 编译命令:
g++-13 barrier.cc -march=native -o barrier -O3 -Wall -std=c++20 - 绑定核心
0-3运行:taskset -c 0-3 ./barrier,性能为11,338,408次完成/秒 - 绑定核心
6-9运行:taskset -c 6-9 ./barrier,性能降至3,962,992次完成/秒 - 仅当同时分配至少一个编号<8和至少一个编号>=8的核心时,才会出现性能下降
测试环境:Ubuntu 22.04,16核AMD Ryzen 9 7950X(已关闭缓解措施、禁用SMT),所有核心拥有独立L1/L2缓存,共享L3缓存。
自旋屏障代码实现
#include <atomic> #include <thread> #include <chrono> #include <iostream> #include <vector> // 自旋等待直到所有线程到达屏障的实现 template <typename Completion> class SpinningBarrier { int64_t count_; std::atomic<int64_t> spaces_ __attribute__((aligned(128))); std::atomic<int64_t> generation_ __attribute__((aligned(128))); Completion completion_cb_; public: // 构造函数:指定参与线程数和完成回调 // 所有线程到达屏障后,最后一个线程执行回调,其他线程阻塞直到回调完成 SpinningBarrier(int64_t count, Completion completion_cb) : count_(count), spaces_(count), generation_(0), completion_cb_(completion_cb) {} void Wait() { int64_t my_generation = generation_; if (--spaces_ == 0) { spaces_ = count_; completion_cb_(); generation_++; } else { while (generation_ == my_generation); } } }; int main() { int64_t x = 0; const int kNumThreads = 4; std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); SpinningBarrier barrier(kNumThreads, [&]{ // 最后到达的线程执行:递增x并定期输出性能 x += 1; if (x % (1024 * 1024) == 0) { auto elapsed = std::chrono::steady_clock::now() - begin; auto micros = std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count() + 1; std::cerr << x * 1000000 / micros << " completions / second\n"; } }); std::vector<std::jthread> threads; for (int i = 0; i < kNumThreads; ++i) { threads.push_back(std::jthread([&] { while(true) { barrier.Wait(); } })); } }
性能差异的核心原因:AMD 7950X的双CCD架构
AMD Ryzen 9 7950X采用**双CCD(核心复合体)**设计:
- 核心0-7属于第一个CCD,共享一块32MB L3缓存
- 核心8-15属于第二个CCD,共享另一块32MB L3缓存
- 两个CCD通过Infinity Fabric连接,跨CCD的缓存/内存访问延迟远高于同CCD内部
自旋屏障的性能瓶颈
自旋屏障的同步完全依赖spaces_和generation_两个原子变量,核心差异在于缓存一致性开销:
- 同CCD核心组(如0-3):所有线程的原子操作都在同一L3缓存域内完成,缓存同步仅需本地CCD内部处理,开销极低,原子操作和自旋等待的延迟都很小。
- 跨CCD核心组(如6-9):线程分布在两个CCD,对原子变量的每次操作(递减、递增、自旋加载)都需要通过Infinity Fabric同步两个CCD的缓存状态,额外的跨片通信延迟直接拖慢了整个屏障循环的速度。
你观察到的"仅混合<8和>=8核心时性能下降",完全匹配双CCD架构的特性:只要线程全部在同一CCD内,缓存同步开销就可控;一旦跨CCD,缓存一致性的额外延迟会大幅降低性能。
优化建议
- 始终将参与自旋屏障的线程绑定到同一个CCD的核心(如0-7或8-15),避免跨CCD分布
- 自旋等待时加入
_mm_pause()指令(x86平台)或std::this_thread::yield(),减少空转对缓存带宽的占用,降低跨场景下Infinity Fabric的负载 - 考虑使用C++20标准库的
std::barrier,其实现针对不同硬件架构做了优化,比自定义自旋屏障更适配NUMA环境
内容的提问来源于stack exchange,提问作者David Frank
相关产品推荐
相关产品推荐

