我正在对我的服务器上的核心到核心延迟进行基准测试,以找到良好的核心关联性。
我正在尝试将两个线程的核心亲和力设置为不同的CPU,并计算线程之间消息的延迟时间。消息通过 传递std::atomic
。运行时间通过以下方式计算https://github.com/fuatu/core-latency-atomic
核心亲和力通过分配(POSIX)
void set_affinity(long cpu_num) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu_num, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
}
运行时间是通过两个线程访问的原子来测量的:
enum State
{
Preparing,
Ready,
Ping,
Pong,
Finish,
};
class Sync
{
public:
State wait_as_long_as(State wait_state)
{
State loaded_state = state.load();
while (loaded_state == wait_state)
loaded_state = state.load();
return loaded_state;
}
void wait_until(State expected_state)
{
while (state.load() != expected_state)
{
}
}
void set(State new_state, State expected_state)
{
//state.store(new_state);
state.compare_exchange_strong(expected_state, new_state);
}
private:
std::atomic<State> state{Preparing};
};
static void set_affinity(unsigned int cpu_num)
{
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu_num, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
}
struct LatencyBench
{
LatencyBench(long first_cpu_, long second_cpu_)
: first_cpu{first_cpu_}
, second_cpu{second_cpu_}
{
}
void operator()(nonius::chronometer meter) const
{
Sync sync;
set_affinity(first_cpu);
std::thread t([&] {
set_affinity(second_cpu);
sync.set(Ready,Preparing);
State state = sync.wait_as_long_as(Ready);
while (state != Finish)
{
//if (state == Ping)
sync.set(Pong,Ping);
state = sync.wait_as_long_as(Pong);
}
});
sync.wait_until(Ready);
// start timer
sync.set(Ping,Ready);
sync.wait_until(Pong);
// stop timer
sync.set(Finish,Pong);
t.join();
}
const long first_cpu;
const long second_cpu;
};
运行时间仅在线程通信期间测量;它们不包括启动时间./a.out
或启动第二个线程的时间。
测量的延迟对于在同一程序中重复试验来说是稳健的;如果我睡个懒觉main
,然后第二次运行分析,则延迟距离第一次测量仍大约在 1 纳秒内。但如果我./a.out
再次运行,测得的延迟可能会改变大约 40 纳秒。这还可以更改具有最佳延迟的核心对。
您知道两次运行相同的可执行文件时这些随机延迟变化背后可能是什么吗?
额外细节:
使用numactl -m 0 -N 0 ./a.out
并专注于 NUMA 节点 0 内的核心对并不能缓解这种情况。
使用配置了子 NUMA 节点的服务器并保留在一个子 NUMA 节点内同样不会改变这一点。延迟的可变性已在 Xeon 和 EPYC 处理器上得到复制。
我希望其中的多次分析main
同样表明这不是缓存问题,因为第二次分析main
可能会缓存所有内容。