X86 英特尔CPU上原子添加操作和缓存线锁定的RFO计数?
我试图理解原子加法运算的本质。因此,我在Broadwell机器上运行以下代码X86 英特尔CPU上原子添加操作和缓存线锁定的RFO计数?,x86,shared-memory,cpu-architecture,perf,atomicity,X86,Shared Memory,Cpu Architecture,Perf,Atomicity,我试图理解原子加法运算的本质。因此,我在Broadwell机器上运行以下代码 int main(int argc, char ** argv){ int nThreads = -1; float shareFrac = -1; uint64_t nIter = -1; ParseArg(argc, argv, nThreads, shareFrac, nIter); atomic<uint64_t> justToAvoidCompilerO
int main(int argc, char ** argv){
int nThreads = -1;
float shareFrac = -1;
uint64_t nIter = -1;
ParseArg(argc, argv, nThreads, shareFrac, nIter);
atomic<uint64_t> justToAvoidCompilerOptimization;
#pragma omp parallel num_threads(nThreads)
{
int me = omp_get_thread_num();
atomic<uint64_t> *tsData = &trueSharingData.data[0];
atomic<uint64_t> *privateData = &(new SharedData_t())->data[0];
for(uint64_t i = 0 ; i < nIter; i++) {
// Use RDTSC as a proxy random number generator
unsigned long lo, hi;
asm volatile( "rdtsc" : "=a" (lo), "=d" (hi) );
int rNum = (lo % 54121) % 100; // mod by a prime.
// if the random number is < shareFrac, perform a shared memory operation
if (rNum < shareFrac) {
*tsData += rNum2;
} else {
*privateData += rNum;
}
}
justToAvoidCompilerOptimization += *tsData;
justToAvoidCompilerOptimization += *privateData;
}
return justToAvoidCompilerOptimization.load() ^ justToAvoidCompilerOptimization.load();
}
int main(int argc,char**argv){
int-nThreads=-1;
浮动股票分数=-1;
uint64_t nIter=-1;
ParseArg(argc、argv、nThreads、shareFrac、nIter);
原子优化;
#pragma omp并行num_线程(n线程)
{
int me=omp_get_thread_num();
原子*tsData=&trueSharingData.data[0];
原子*私有数据=&(新的共享数据())->数据[0];
对于(uint64_t i=0;i
在这段代码中,基本上每个线程执行原子加法操作nIter
的次数,nIter
是循环跳闸计数。在每个循环迭代中,可以对共享内存位置或线程局部变量执行原子添加操作
用于在共享内存位置上执行原子添加操作的循环跳闸计数的分数由参数shareFrac
确定。例如,如果shareFrac
为0.3,而nIter
为1000,则预计将在共享内存位置上执行大约300次原子添加
所以,我做了一个小实验,在这个实验中,我用
shareFrac
的值来运行这个简单的代码多次。对于每次运行,我使用perf统计L2_RQSTS.RFO_未命中事件的发生次数。我还将perf给出的计数与预期计数进行比较。预期的计数只是nthreads*nIter*shareFrac
结果如下
nThreads=2,nIter=1亿
nThreads=8,nIter=1亿
如图所示,在大多数运行中,RFO未命中计数超过预期计数。这怎么可能呢??一种可能的解释是,原子add带来一行RFO,希望读取并更新。
但是,在读写之间,该行可能被盗,在这种情况下,必须将该行取回。但是,据我所知,对于x86上的原子操作,缓存线是锁定的,因此,缓存线在获得独占许可后不得被盗。还是我的理解不正确
为了消除由于预取而导致缓存线传输的可能性,在得到这些结果之前,我还消除了机器所有内核上的h/w预取器。我认为当前Intel总是无条件地为原子操作锁定缓存线的假设,因此,二级未命中的数量应该根据访问次数准确预测,但可能不准确 例如,的背景描述了锁定指令的“传统”机制,即直接背对背和在失效时执行指令的锁定/加载和解锁/存储部分,以便相关联的行可以容易地在整个时间内保持锁定状态。我认为,这大致符合您对其工作方式的描述,如果它仅以这种方式工作,您可能会期望二级RFO未命中遵循预期的路线 然而,专利本身描述了一种放松锁定要求的机制。特别是,提前执行操作的加载/锁定部分,基本上是作为普通加载,并推测在加载执行和存储提交之间的时间内关联的缓存不会被“窃取”。如果确实发生了这样一条被盗的缓存线,则需要重新执行该操作。用专利中英特尔的话说: 但是,如果预测是特定的锁定指令 事实上不会被竞争,那么就有可能继续下去 推测性发布的正常负载微操作和监控 与监视器逻辑116相关的存储器位置,以确定 是否出现任何争议迹象。因此,我们实际上可能不会 执行读修改写部件时锁定内存位置 执行原子性指令,但执行部分 分别观察可能表明 另一个处理器或线程可能破坏了 原子性。此类争用指示可包括对缓存的窥探 包含加载指令的目标地址的行 中断,或者如果后续的存储解锁微操作在 缓存 在一些实施例中,监视器逻辑116可以监视多个监视器 处理器中存在的现有逻辑信号。如无异议 指示出现在代表等效物的时间段内 锁定条件下,然后投机性发布正常负载 微操作可以正常退出。这可能会导致故障 执行lock指令并提高处理器性能。 但是,如果确实出现争用迹象,管道可能必须 刷新并重新执行锁定指令 这只是一个小摘录,但抓住了相关的想法:尝试以与无序执行更兼容的方式执行锁,如果失败,则采用更保守的方法重试。这项专利继续被授予专利