C 缓存写入是否需要更长的时间才能使缓存失效?
当有更多的内核/缓存保存该行的副本时,是否需要更长的时间才能完成缓存写入。 我还想测量/量化实际需要多长时间 我在谷歌上找不到任何有用的东西,我自己也很难测量它,也无法解释我测量的内容,因为在现代处理器上可能会发生很多事情。 (重新排序、预取、缓冲和天知道是什么) 详细信息: 我测量它的基本过程大致如下:C 缓存写入是否需要更长的时间才能使缓存失效?,c,parallel-processing,cpu-cache,C,Parallel Processing,Cpu Cache,当有更多的内核/缓存保存该行的副本时,是否需要更长的时间才能完成缓存写入。 我还想测量/量化实际需要多长时间 我在谷歌上找不到任何有用的东西,我自己也很难测量它,也无法解释我测量的内容,因为在现代处理器上可能会发生很多事情。 (重新排序、预取、缓冲和天知道是什么) 详细信息: 我测量它的基本过程大致如下: write soemthing to the cacheline on processor 0 read it on processors 1 to n. rdtsc write it on
write soemthing to the cacheline on processor 0
read it on processors 1 to n.
rdtsc
write it on process 0
rdtsc
我甚至不确定在进程0上实际使用哪些指令进行读/写,以确保在最终时间测量之前完成写/失效
目前我正在处理一个原子交换(uuu sync_fetch_和_add()),但线程的数量本身似乎对这个操作的长度很重要(而不是要失效的线程的数量)——这可能不是我想要测量的
我还尝试了先读,然后写,然后内存障碍(\uuuu sync\u synchronize())。这看起来更像我期望看到的,
但在这里,我也不确定当最终rdtsc发生时,写入是否完成
您可以猜到,我对CPU内部的知识有点有限
非常感谢您的帮助
附言:
*我使用linux、gcc和pthreads进行测量。
*我想知道这一点,为我的并行算法建模
编辑:
在一周左右的时间里(明天去度假),我会做更多的研究,把我的代码和笔记贴在这里,并链接到这里(以防有人感兴趣),因为我花在这上面的时间有限。我开始写一个很长的答案,详细描述这是如何工作的,然后意识到,我可能对确切的细节知之甚少。所以我会做一个简短的回答 因此,当您在一个处理器上写入某个数据时,如果它不在该处理器缓存中,则必须将其取出,并且在处理器读取数据后,它将执行实际的写入操作。执行此操作时,它将向系统中的所有其他处理器发送缓存失效消息。这些将丢弃任何内容。如果另一个处理器有“脏”内容,它将自己写出数据,并要求失效-在这种情况下,第一个处理器必须在完成写入之前重新加载数据(否则,同一缓存线中的其他元素可能会被破坏) 在对该缓存线感兴趣的每个其他处理器上,都需要将其读回缓存 _u_sync_fetch_和_add()将使用“lock”前缀[在x86上,其他处理器可能会有所不同,但支持“每条指令”锁的处理器的总体思路大致相同]-这将发出“我只想要此缓存线,其他所有人请放弃并使其无效”。就像第一种情况一样,处理器可能需要重新读取其他处理器可能弄脏的任何内容 内存屏障不能确保数据“安全地”更新——它只会确保“在本指令完成时,所有处理器都能看到以前发生的(内存)情况” 优化处理器使用的最佳方法是尽可能少地共享,尤其是避免“虚假共享”。在许多年前的一个基准中,有一个类似于[简化]的结构:
struct stuff {
int x[2];
... other data ... total data a few cachelines.
} data;
void thread1()
{
for( ... big number ...)
data.x[0]++;
}
void thread2()
{
for( ... big number ...)
data.x[1]++;
}
int main()
{
start = timenow();
create(thread1);
create(thread2);
end = timenow() - start;
}
由于每次thread1写入x[0]时,thread2的处理器都必须删除它的x[1]副本,反之亦然,结果是SMP测试[vs只是运行thread1]的速度慢了大约15倍。通过如下方式更改结构:
struct stuff {
int x;
... other data ...
} data[2];
及
我们得到了200%的1线程变体[给或拿几个百分比]
对,因此处理器有缓冲队列,当处理器写入内存时,写入操作存储在缓冲队列中。内存屏障(mfence、sfence或lfence)指令用于确保在处理器继续执行下一条指令之前,所有未完成的读/写、写或读类型操作都已完全完成。通常情况下,处理器只需通过下面的任何指令,以愉快的方式继续运行,最终以某种方式完成内存操作。由于现代处理器到处都有大量的并行操作和缓冲区,因此可能需要相当长的一段时间才能让某些东西真正慢慢流到它最终会到达的地方。因此,在关键的时候,确保在继续之前确实做了一些事情(例如,如果我们已经向视频内存写入了一组指令,现在我们想开始运行这些指令,我们需要确保“指令”写入实际上已经完成,而处理器的其他部分还没有完成。因此,使用
sfence
确保写入已经完成真的发生了-这可能不是一个非常现实的例子,但我想你明白了。)缓存写入必须在弄脏缓存线之前获得行所有权。这取决于
在处理器体系结构中实现的缓存一致性模型,此步骤所需的时间各不相同。我所知道的最常见的一致性协议有:
- 窥探一致性协议:所有缓存监控缓存内存线的地址线,即所有内存请求必须广播到所有CPU,即随着CPU的增加不可扩展
- 基于目录的一致性协议:在多个cpu之间共享的所有缓存线都保存在一个目录中;因此,使所有权失效/获得所有权是一个点对点cpu请求,而不是一个广播,即更具可扩展性,但延迟会受到影响,因为目录是一个争用点
void thread1()
{
for( ... big number ...)
data[0].x++;
}