【问题标题】:Does a cache write take longer with more caches to invalidate?缓存写入是否需要更长的时间才能使更多缓存失效?
【发布时间】:2012-12-26 17:54:58
【问题描述】:

当有更多的核心/缓存持有该行的副本时,您能否帮助我确定缓存写入是否需要更长的时间才能完成。 我还想测量/量化实际需要多长时间。

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

详情:

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

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 内部的了解有些有限。

非常感谢任何帮助!

ps: * 我使用 linux、gcc 和 pthreads 进行测量。 * 我想知道这一点,以便为我的并行算法建模。

编辑:

大约一周后(明天去度假)我会做更多的研究并发布我的代码和注释并将其链接到此处(以防有人感兴趣),因为我可以花在这方面的时间是有限的。

【问题讨论】:

  • 如果您的 gcc 版本足够新,您可能需要使用改进很多的 __atomic 内置函数。您可能还想阅读MESI 缓存一致性协议。它不是唯一的协议,但它会让您更好地了解原子操作是如何实现的。

标签: c parallel-processing cpu-cache


【解决方案1】:

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

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

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

__sync_fetch_and_add() 将使用“锁定”前缀 [在 x86 上,其他处理器可能会有所不同,但支持“每指令”锁定的处理器的总体思路大致相同] - 这将发出“我想要这个cacheline 独家,其他人请放弃它并使其无效”。就像第一种情况一样,处理器很可能必须重新读取另一个处理器可能弄脏的任何内容。

内存屏障不能确保“安全”地更新数据——它只会确保“在此指令完成时,所有处理器都可以看到之前发生的(内存)”。

优化处理器使用的最佳方法是尽可能少地共享,尤其是避免“错误共享”。在多年前的基准测试中,有一个类似 [simplifed] 的结构:

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];

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

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

是的,所以处理器有缓冲区队列,当处理器写入内存时,写入操作被存储在其中。内存屏障(mfence、sfence 或 lfence)指令用于确保任何未完成的读/写、写或读类型操作在处理器继续执行下一条指令之前已完全完成。通常,处理器将通过任何后续指令继续其愉快的方式,并最终以某种方式完成内存操作。由于现代处理器在各处都有大量并行操作和缓冲区,因此可能需要相当长的时间才能真正渗透到最终到达的地方。因此,当确保在继续之前确实已经完成某些事情是至关重要的(例如,如果我们已经向视频内存写入了一堆指令,并且我们现在想要开始运行这些指令,我们需要让确保“指令”写入实际上已经完成,并且处理器的其他部分还没有完成它。所以使用sfence 来确保写入确实发生了——这可能不太现实例如,但我想你明白了。)

【讨论】:

  • 那么原子交换是测量的正确操作吗?在我的测量中,似乎不管要失效的核心数量是多少,线程的总数对此都很重要(程序中产生的空闲线程的数量——这很奇怪,不是吗?)。
  • 您并没有真正发布太多关于您的期望的详细信息,以及这些数字与您的期望有何不同。如果您不使用锁定的 (__sync...) 操作,您会发现有什么不同吗?
  • 如果我做一个读、写、内存屏障(__sync_synchronize)它不会随着空闲线程数的增加而增加,但它会随着核心数的失效而增加(这是我所期望的——但是我不确定这是否是我想要测量的?是吗?我不知道。)。如果只是读,写它会被魔法消失,我只得到 rdtsc 循环本身〜20-40,没有任何区别。我将在这上面多花几个小时(一周左右),但由于这不是一个非常重要的问题,所以我的时间有限。但如果你愿意,我可以在最后贴出我的东西和笔记。
  • 我很确定,sync_synchronise 只会让处理器一直保持到写入缓冲区被清除——这可能需要一些时间。无论如何,发布更多信息!
  • 我会在一周后度假回来。我希望你能笼统地回答它。我什至不确定“写缓冲区已被清除”是什么意思。我希望它确保缓存写入发生,并且所有其他缓存都已失效......我可以使用哪些指令来确保这一点?
【解决方案2】:

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

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

大多数 CPU 架构都支持称为 PMU(性能监控单元)的东西。本机出口 许多计数器,例如:缓存命中、未命中、缓存写入延迟、读取延迟、tlb 命中等。请查阅 cpu 手册以查看此信息是否可用。

【讨论】:

  • 那么现代 x86 处理器做了什么?更多核心是否需要更长的时间?我该如何测量它?
猜你喜欢
  • 2015-12-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-11
  • 1970-01-01
  • 2022-08-14
  • 1970-01-01
  • 1970-01-01
  • 2020-12-19
相关资源
最近更新 更多