RFO计入英特尔CPU上的原子添加操作和缓存线锁定?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RFO计入英特尔CPU上的原子添加操作和缓存线锁定?相关的知识,希望对你有一定的参考价值。

我试图了解原子添加操作的本质。所以,我在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> 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();
}

在这段代码中,基本上每个线程执行原子添加操作nIter次数,其中nIter是循环行程计数。在每次循环迭代中,可以在共享内存位置或线程局部变量上执行原子添加操作。

用于在共享存储器位置上执行原子添加操作所花费的循环行程计数的分数由参数shareFrac确定。例如,如果shareFrac为0.3且nIter为1000,则预计在共享内存位置上执行大约300次原子添加。


所以,我进行了一个小实验,我用shareFrac值增加了很多次运行这个简单的代码。对于每次运行,我使用perf计算L2_RQSTS.RFO_MISS事件的发生次数。我还将perf给出的计数与预期计数进行比较。预期的数量只是nthreads * nIter * shareFrac

结果如下。

来确定nthreads = 2黑夜= 100个百万 nThreads = 2, nIter = 100 millions

来确定nthreads = 8,黑夜= 100个百万 nThreads = 8, nIter = 100 millions

从图中可以看出,RFO未命中计数超过了大多数运行中的预期计数。这怎么可能??一个可能的解释是,原子添加带来了一条RFO线,希望能够进行读取和更新。但是,在读取和写入之间可能会盗取线路,在这种情况下,必须将线路带回。但是,据我所知,对于x86上的原子操作,高速缓存行被锁定,因此,一旦获得独占权限,高速缓存行不得被盗。或者我的理解不正确?

为了消除由于预取而导致高速缓存行传输的可能性,我还在获得这些结果之前消除了机器所有内核上的h / w预取程序。

答案

我认为当前的英特尔总是无条件地锁定高速缓存行以进行原子操作的假设,因此L2未命中的数量应根据访问次数准确预测,可能不准确。

例如,this Intel patent的背景描述了锁定指令的“常规”机制,即直接背靠背和退休时执行指令的锁定/加载和解锁/存储部分,以便关联的行可以很容易地在整个时间内处于锁定状态。我认为,这与你描述它的工作方式大致相符,如果它只是这样工作,你可能会期望L2 RFO未命中符合预期的线。

然而,该专利本身描述了一种松开锁定要求的机制。特别是,早期执行操作的加载/锁定部分,基本上作为普通加载,并推测在加载执行和存储提交之间的时间内相关的高速缓存不会被“窃取”。如果确实发生了这样的被盗高速缓存行,则需要重放该操作。用英特尔的专利来说:

然而,如果预测是特定锁定指令实际上不会被争用,那么可以继续推测发出的正常负载微操作并用监视器逻辑116监视有关的存储器位置以确定是否有争议的迹象出现了。因此,在执行指令的读取 - 修改 - 写入部分以强制执行原子性时,我们实际上可能不会锁定存储器位置,而是在观察指示另一个处理器或线程可能已经破坏了感知的条件的同时单独执行这些部分。原子。这种竞争指示可以包括对高速缓存行的窥探,其包括加载指令的目标地址,中断,或者如果后续的store_unlock微操作在高速缓存中未命中。

在一些实施例中,监视器逻辑116可以监视处理器内存在的若干现有逻辑信号。如果在表示等效锁定状态的时间段期间没有出现竞争指示,则推测发出的正常负载微操作可以正常退出。这可能允许无序执行锁定指令并增强处理器性能。但是,如果确实出现竞争指示,则可能必须刷新管道并重新执行锁定指令。

这只是一个小的摘录,但捕获了相关的想法:尝试以与乱序执行更兼容的方式执行锁定,如果失败,则重试采取更保守的方法。该专利继续解释预测因子如何工作,与分支预测类比。基本方法只是跟踪每个IP的争用行为。

这可以解释为什么额外的RFO事件在100%的shareFrac附近变为零:此时线路的争用性足以使得尝试更积极的锁定实现的启发式/预测器不被触发,因此它总是需要保守的路径。

您可以通过检测缺少或存在无序执行的测试来确认该理论,并表明当RFO请求的数量增加时,也会发生一些OoO执行。

以上是关于RFO计入英特尔CPU上的原子添加操作和缓存线锁定?的主要内容,如果未能解决你的问题,请参考以下文章

总线锁定与一致性缓存

并发编程,高速缓存,原子操作,指令重排序

并发编程,高速缓存,原子操作,指令重排序

原子操作成本

原子操作的硬件与java实现

编程篇3:内存屏障和CPU缓存