非时序指令是如何工作的?

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”,但据我所知,流内置函数会绕过缓存 - 既没有缓存读取也没有缓存写入。这段代码如何访问任何缓存行?第二个加粗片段说得类似,即该函数“避免在写入之前读取缓存行”。如上所述,我看不到任何缓存何时以及何时被写入。另外,任何对缓存的写入是否需要先进行缓存写入?能否有人向我澄清这个问题?

你对 SSE 缓存操作的假设有参考资料吗? 英特尔文档 指定了“污染”,这也是 Ulrich 在评论中提到的内容。 - Steve-o
我的知识都来自于Ulrich的论文。在本章的早些时候,他写道:“这些非时间相关的写操作不会读取缓存行然后修改它;相反,新内容直接写入内存。”。这是“6.1绕过缓存”的第二段。 - Pawel Batko
2
我不清楚他想说什么,但是如果MOVNTDQ恰好包含该地址,则会更新缓存。 - Hans Passant
@HansPassant: 根据英特尔手册vol1 ch 10.4.6.2 Caching of Temporal vs. Non-Temporal Datamovntdq可以在缓存中命中,但是如果该行存在,则会将其从缓存中驱逐。我猜这个设计决策是为了让驱动程序在对视频内存进行NT存储后避免使用clflush。(如果我没记错的话,文档说早期支持该指令的CPU上不会发生这种保证性的驱逐。) - Peter Cordes
"_mm_set1_epi8(c)"比打16次"c"要简单得多,它可以更轻松地广播一个字节。 - Peter Cordes
3个回答

3

当你向内存写入数据时,需要将要写入的缓存行加载到高速缓存中,以防只部分写入缓存行。

当你向内存写入数据时,数据将被分组并存储在存储缓冲区中。通常,一旦缓冲区满了,数据会立即被刷新到高速缓存或内存中。请注意,存储缓冲区的数量通常很小(约为4个)。连续写入同一地址将使用相同的存储缓冲区。

使用非临时提示进行数据流式读/写通常用于减少高速缓存污染(通常使用WC内存)。其思想是在CPU上为这些指令保留一小组高速缓存行。将缓存行加载到此较小的缓存中而不是加载到主要高速缓存中。

以下内容假定以下行为(但我找不到任何参考资料证明硬件实际执行此操作,需要测量或可靠来源,它可能因硬件而异): - 一旦CPU检测到存储缓冲区已满并且已对齐到缓存行,则由于非临时写入跳过了主要高速缓存,直接将其刷新到内存中。

此方法能够奏效的唯一方式是在刷新时将存储缓冲区与实际写入的缓存行合并。这是一个公平的假设。

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

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

如果使用非临时写入来部分写入缓存行,则可能需要从主内存(或主高速缓存,如果存在)中获取缓存行,并且如果我们没有通过正常读取或非临时读取(将其放入单独的高速缓存中)预先读取缓存行,则可能会变得非常慢。

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

总之,最后一条指令触发了写入操作,因为它同时填满了存储缓冲区。由于硬件知道存储缓冲区是连续的并与缓存行对齐,因此存储缓冲区刷新可以避免读取写入的缓存行。非临时写入提示仅在仅当缓存行不在主要高速缓存中时才避免将其写入主要高速缓存。


1
NT 存储在缓存中的命中会使缓存行失效。如果我没记错,当前的 Intel 设计每个核心有 10 个填充缓冲区。你提到的 4-8 可能来自旧的信息源,或者我记错了。但是,总体上完全填满填充缓冲区将触发其刷新,这一点我理解是正确的。 - Peter Cordes

1
我认为这部分是一个术语问题:Ulrich Drepper文章中引用的段落并不是在谈论缓存数据。它只是使用“缓存行”一词表示对齐的64B块。
这是正常的,特别是在讨论具有不同缓存行大小的硬件范围时非常有用。(早期的x86 CPU,最近的PIII,具有32B的缓存行,因此使用这个术语避免将该微架构设计决策硬编码到讨论中。)
即使缓存行当前没有在任何缓存中热门,它仍然是缓存行的数据。

-2
我手头没有参考资料来证明我所说的,但我的理解是:内存总线上的唯一传输单元是缓存行,无论它们是进入缓存还是进入某些特殊寄存器。因此,你粘贴的代码确实填充了一个缓存行,但它是一个不驻留在缓存中的特殊缓存行。一旦修改了这个缓存行的所有字节,该缓存行将直接发送到内存,而不经过缓存。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接