You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自旋屏障代码在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_两个原子变量,核心差异在于缓存一致性开销:

  1. 同CCD核心组(如0-3):所有线程的原子操作都在同一L3缓存域内完成,缓存同步仅需本地CCD内部处理,开销极低,原子操作和自旋等待的延迟都很小。
  2. 跨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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 18:58:10