我正在进行一个测试来测量CPU不同核心之间的消息同步延迟。具体来说,我正在测量 CPU2 需要多少个时钟周期来检测 CPU1 共享数据的变化。 CPU1和CPU2都使用
rdtsc
指令来记录时序。我观察到 Intel 和 AMD CPU 平台之间的行为不一致。欢迎您对此问题有任何想法或建议。
代码如下。该程序包含两个线程“ping”和“pong”,分别在CPU1 和CPU2 上运行。正如他们的名字所暗示的,他们轮流增加自己的共享数据(
shd_data
)来实现乒乓循环,并最终测量总运行时间(ts_end-ts_start
和tb_end-tb_start
中的最小值)。为了也测量“ping”操作的平均时间,我添加了 rdtsc(tsc_start[loop]);
和 rdtsc(tsc_end[loop])
;在 while 循环内。然而,这两行监控代码显着影响了总运行时间,将平均乒乓循环时间从大约 180 个周期增加到 290 个周期。对此我找不到合理的解释。
#define MAX_LOOPS 10
#define BIDX 0
#define SIDX 15
static volatile int start_flag = 0;
static int shd_data[16];
unsigned long tb_start , tb_end, ts_start, ts_end;
unsigned long gap[12];
unsigned long tsc_start[MAX_LOOPS];
unsigned long tsc_end[MAX_LOOPS];
void* ping(void *args)
{
int loop=0;
int old = 0;
int cur = 0;
memset(tsc_start, 0, MAX_LOOPS*sizeof(unsigned long));
memset(shd_data, 0, 64); // preheat
while (start_flag == 0) ; // sync. start
rdtsc(tb_start);
while (++loop < MAX_LOOPS) {
rdtsc(tsc_start[loop]);
WRITE_ONCE(shd_data[BIDX], shd_data[BIDX]+1);
do {
cur = READ_ONCE(shd_data[SIDX]);
} while (cur <= old);
old = cur;
}
WRITE_ONCE(shd_data[BIDX], shd_data[BIDX]+1);
rdtsc(tb_end);
return NULL;
}
void* pong(void *args)
{
int loop=0;
int old = 0;
int cur = 0;
memset(tsc_end, 0, MAX_LOOPS*sizeof(unsigned long));
memset(shd_data, 0, 64); //preheat
while (start_flag == 0) ; // sync. start
rdtsc(ts_start);
while (++loop < MAX_LOOPS) {
do {
cur = READ_ONCE(shd_data[BIDX]);
rdtsc(tsc_end[loop]);
} while (cur <= old);
old = cur;
WRITE_ONCE(shd_data[SIDX], shd_data[SIDX]+1);
}
WRITE_ONCE(shd_data[SIDX], shd_data[SIDX]+1);
rdtsc(ts_end);
return NULL;
}
实验机是AMD 3910X,带有gcc-9.4.0。我还在 Intel i9-9900 CPU 上运行了相同的代码。有趣的是,使用 rdtsc 代码监控“ping”操作并没有影响整体乒乓时间,仍保持在 400 个周期左右。我不确定这种现象是否可以在其他 AMD 或 Intel CPU 上复制,因为我目前只能访问这两台机器。
更新:
通过调整CPU频率缩放,我的两台机器的CPU频率固定在3.6GHz的基础频率。
您可以从下面的链接找到完整的可运行代码:
简要回答:缓存行争用
添加的监控代码,比如问题中提到的
rdtsc()
的使用,会导致数据段中变量布局的改变,导致运行时缓存行争用。修改“MAX_LOOPS”定义的值也会有同样的效果。应该注意的是,缓存争用也可能是由推测执行引起的。
如何修复
将
__attribute__((aligned(64)))
添加到 shd_data[32]
的定义中可确保其在缓存行上对齐。
限制
上述情况特定于AMD 3970X CPU,也可能适用于Zen2架构或其他AMD CPU。
我也在Intel i9-9900K上进行了这个实验,两个程序的数据段布局是相同的。但是,我还没有在 Intel CPU 上观察到缓存抖动,这是我还没有完全理解的事情。