9

我正在阅读Ulrich Drepper 的每个程序员应该了解的关于内存的知识 pdf。在第 6 部分的开头有一段代码:

#include <emmintrin.h>
void setbytes(char *p, int c)
{
    __m128i i = _mm_set_epi8(c, c, c, c,
    c, c, c, c,
    c, c, c, c,
    c, c, c, c);
    _mm_stream_si128((__m128i *)&p[0], i);
    _mm_stream_si128((__m128i *)&p[16], i);
    _mm_stream_si128((__m128i *)&p[32], i);
    _mm_stream_si128((__m128i *)&p[48], i);
}

在它下面有这样的评论:

假设指针p正确对齐,调用此函数会将寻址缓存行的所有字节设置为c. 写组合逻辑将看到四个生成的 movntdq 指令,并且仅在执行完最后一条指令后才向内存发出写命令。总而言之,这个代码序列不仅避免了在写入之前读取缓存行,还避免了可能很快不需要的数据污染缓存。

让我感到困扰的是,在对函数的注释中写道,它“会将寻址缓存行的所有字节设置为 c”,但根据我对流内部结构的理解,它们绕过缓存 - 既没有缓存读取也没有缓存写入。此代码将如何访问任何缓存行?第二个粗体片段表示类似,该函数“避免在写入之前读取缓存行”。如上所述,我看不到任何缓存的写入方式和时间。此外,是否需要在缓存写入之前对缓存进行任何写入?有人可以向我澄清这个问题吗?

4

3 回答 3

3

当您写入内存时,您写入的缓存行必须首先加载到缓存中,以防您仅部分写入缓存行。

当您写入内存时,存储在存储缓冲区中分组。通常,一旦缓冲区已满,它将被刷新到缓存/内存。请注意,存储缓冲区的数量通常很小(~4)。对地址的连续写入将使用相同的存储缓冲区。

带有非临时提示的流式读/写通常用于减少缓存污染(通常使用 WC 内存)。这个想法是在 CPU 上保留一小组缓存线供这些指令使用。不是将缓存线加载到主缓存中,而是将其加载到这个较小的缓存中。

该评论假设以下行为(但我找不到硬件实际执行此操作的任何参考,需要测量或可靠来源,并且它可能因硬件而异): - 一旦 CPU 看到存储缓冲区已满并且它与缓存行对齐,它将直接刷新到内存,因为非临时写入绕过了主缓存。

唯一可行的方法是,如果存储缓冲区与实际写入的缓存行在刷新后发生合并。这是一个公平的假设。

请注意,如果写入的缓存行已经在主缓存中,上述方法也会更新它们。

如果使用常规内存写入而不是非临时写入,则存储缓冲区刷新也会更新主缓存。这种情况完全有可能避免读取内存中的原始缓存行。

如果使用非临时写入写入部分缓存行,则可能需要从主内存(或主缓存,如果存在)中获取缓存行,如果我们没有提前读取缓存行,可能会非常慢使用常规读取或非临时读取(这会将其放入我们单独的缓存中)。

通常,非临时高速缓存大小约为 4-8 高速缓存行。

总而言之,最后一条指令开始写入,因为它也恰好填满了存储缓冲区。存储缓冲区刷新可以避免读取写入的高速缓存行,因为硬件知道存储缓冲区是连续的并且与高速缓存行对齐。非临时写入提示仅用于避免使用我们写入的缓存行 IF 填充主缓存,并且仅当它尚未在主缓存中时。

于 2016-05-01T20:09:57.487 回答
1

我认为这部分是一个术语问题:您从 Ulrich Drepper 的文章中引用的段落不是在谈论缓存数据。它只是使用术语“缓存线”来表示对齐的 64B 块。

这是正常的,并且在讨论具有不同缓存行大小的一系列硬件时特别有用。(早期的 x86 CPU,最近是 PIII,有 32B 缓存线,所以使用这个术语可以避免将微架构设计决策硬编码到讨论中。)

数据的缓存行仍然是缓存行,即使它当前在任何缓存中都不热。

于 2016-05-02T04:25:30.927 回答
-2

我没有参考资料来证明我在说什么,但我的理解是:内存总线上的唯一传输单元是高速缓存行,无论它们进入高速缓存还是某些特殊寄存器。所以确实,您粘贴的代码填充了一个缓存行,但它是一个不驻留在缓存中的特殊缓存行。一旦这个缓存行的所有字节都被修改,缓存行被直接发送到内存,而不通过缓存。

于 2014-10-23T14:24:33.187 回答