c语言 - 缓存写入是否需要更长的时间,缓存会更多才能失效



您能否帮助我找出当有更多内核/缓存保存该行的副本时,缓存写入是否需要更长的时间才能完成。 我还想衡量/量化实际需要多长时间。

我在谷歌上找不到任何有用的东西,我自己很难测量它并解释我测量的内容,因为在现代处理器上可能发生许多事情。 (重新排序,预取,缓冲,天知道是什么)

详:

我测量它的基本过程大致如下:

write soemthing to the cacheline on processor 0
read it on processors 1 to n.
rdtsc
write it on process 0
rdtsc

我什至不确定实际使用哪些指令在进程 0 上进行读/写,以确保在最终时间测量之前完成写入/无效。

目前我摆弄着原子交换(__sync_fetch_and_add()),但似乎线程数本身对于此操作的长度很重要(而不是要失效的线程数)——这可能不是我想要测量的?!.

我还尝试了读取,然后写入,然后是内存屏障(__sync_synchronize())。这看起来更像是我期望看到的, 但在这里我也不确定在最终 rdtsc 发生时写入是否完成。

正如您可以猜到的那样,我对CPU内部的了解有些有限。

任何帮助都非常感谢!

附言: * 我使用 linux、gcc 和 pthreads 进行测量。 * 我想知道这个来建模我的并行算法。

编辑:

在一周左右(明天去度假)后,我会做更多的研究,发布我的代码和笔记,并将其链接到这里(如果有人感兴趣),因为我可以花在这件事上的时间是有限的。

我开始写一个很长的答案,准确地描述了它是如何工作的,然后意识到,我可能对确切的细节知之甚少。所以我会做一个更简短的答案。

因此,当您在一个处理器上写入某些内容时,如果它尚未在该处理器缓存中,则必须将其提取,并且在处理器读取数据后,它将执行实际写入。这样做时,它将向系统中的所有其他处理器发送缓存无效消息。然后这些将丢弃任何内容。如果另一个处理器有"脏"内容,它本身会写出数据,并要求失效 - 在这种情况下,第一个处理器必须在完成写入之前重新加载数据(否则,同一缓存行中的其他元素可能会被销毁)。

在对该缓存行感兴趣的所有其他处理器上都需要将其读回缓存。

__sync_fetch_and_add() wilol 使用"锁"前缀 [在 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 只运行线程 1] 运行速度慢了大约 15 倍。通过像这样改变结构:

struct stuff {
int x;
... other data ... 
} data[2];

void thread1()
{
for( ... big number ...)
data[0].x++;
}

我们得到了 200% 的 1 线程变体 [给或拿几个百分点]

是的,因此处理器具有缓冲区队列,当处理器写入内存时,写入操作将存储在其中。内存屏障(mfence,sfence或lfence)指令用于确保在处理器继续下一条指令之前,任何未完成的读/写,写入或读取类型操作已完全完成。通常,处理器只会通过任何以下指令继续以愉快的方式继续,最终内存操作以某种方式完成。由于现代处理器到处都有很多并行操作和缓冲区,因此可能需要相当长的时间才能真正渗透到最终结束的地方。因此,当确保在继续之前确实已经完成某些操作至关重要时(例如,如果我们向视频内存编写了一堆指令,并且我们现在想要开始运行这些指令,我们需要确保"指令"写入实际上已经完成,并且处理器的其他部分尚未完成。因此,请使用sfence来确保写入确实发生了 - 这可能不是一个非常现实的例子,但我认为你明白了。

缓存写入必须在弄脏缓存行之前获得行所有权。取决于 缓存一致性模型在处理器体系结构中实现,此步骤所需的时间各不相同。我知道的最常见的一致性协议是:

  • 侦听一致性协议:所有缓存监控缓存内存行的地址行,即所有内存请求都必须广播到所有CPU,即随着CPU的增加而不可扩展。
  • 基于目录的一致性协议:许多CPU之间共享的所有缓存行都保存在一个目录中;因此,无效/获得所有权是点对点CPU请求而不是广播,即更具可扩展性,但延迟受到影响,因为目录是单点争用。

大多数 CPU 体系结构都支持称为 PMU(性能监视单元)的东西。此单位导出 许多事情的计数器,例如:缓存命中、未命中、缓存写入延迟、读取延迟、TLB 命中等。请参阅 CPU 手册以查看此信息是否可用。

最新更新