是否可以知道缓存未命中的地址?

Met*_*est 7 caching x86-64 processor

每当发生缓存未命中时,是否可以知道丢失的缓存行的地址?现代处理器中是否有可以提供此类信息的硬件性能计数器?

Bee*_*ope 5

是的,在现代的英特尔硬件上,存在精确的内存采样事件,这些事件不仅跟踪指令的地址,还跟踪数据的地址。这些事件还包括大量其他信息,例如满足存储器访问级别的高速缓存层次结构级别,总延迟等。

您可以perf mem用来采样此信息并生成报告。

例如,以下程序:

#include <stddef.h>

#define SIZE (100 * 1024 * 1024)

int p[SIZE] = {1};

void do_writes(volatile int *p) {
    for (size_t i = 0; i < SIZE; i += 5) {
        p[i] = 42;
    }
}

void do_reads(volatile int *p) {
    volatile int sink;
    for (size_t i = 0; i < SIZE; i += 5) {
        sink = p[i];
    }
}

int main(int argc, char **argv) {
    do_writes(p);
    do_reads(p);
}
Run Code Online (Sandbox Code Playgroud)

编译为:

g++  -g -O1 -march=native   perf-mem-test.cpp   -o perf-mem-test
Run Code Online (Sandbox Code Playgroud)

并运行:

sudo perf mem record -U ./perf-mem-test && sudo perf mem report
Run Code Online (Sandbox Code Playgroud)

生成按延迟排序的内存访问报告,如下所示:

perf-mem报告输出

Data Symbol列显示了负载目标地址的位置-在此处大部分显示为p+0xa0658b4,这意味着在0xa0658b4距开始位置偏移一定的距离,p这是因为代码正在读取和写入p。该列表按“本地权重”排序,“本地权重”是参考周期1中的访问延迟。

请注意,记录的信息只是内存访问的一个示例:记录每个未命中通常是过多的信息。此外,默认情况下,它仅记录延迟为30个周期或更长的负载,但是您显然可以使用命令行参数对其进行调整。

如果您只对在所有级别的缓存中丢失的访问感兴趣,那么您会在寻找“本地RAM命中”行2。也许您可以将采样限制为仅缓存未命中-我很确定英特尔内存采样功能支持这一点,并且我认为您可以告诉perf mem我们仅查看未命中。

最后,请注意,这里我使用的-U参数后面record仅指示perf mem记录用户空间事件。默认情况下,它将包括内核事件,这可能对您有用或可能不有用。对于示例程序,有许多内核事件与将p数组从二进制文件复制到可写进程内存有关。

请记住,我对程序进行了专门安排,以使全局数组p最终出现在初始化的.data部分(二进制文件为〜400 MB!),以便它在清单中显示正确的符号。您的进程大部分时间将访问动态分配的或堆栈的内存,这只会给您一个原始地址。是否可以将此映射回有意义的对象取决于您是否跟踪足够的信息以使之成为可能。


1认为它处于参考周期,但是我可能错了,内核可能已经将其转换为纳秒了?

2这里的“本地”和“命中”部分是指我们命中了连接到当前内核的RAM,即,我们没有转到多插槽NUMA配置中与另一个插槽关联的RAM。


Had*_*ais 1

如果您想知道每个的确切虚拟或物理地址缓存未命中的确切虚拟或物理地址,这将非常困难,有时甚至是不可能的。但您更有可能对昂贵的内存访问模式感兴趣;这些模式会因为在一级或多级缓存子系统中丢失而导致较大的延迟。请注意,重要的是要记住,一个处理器上的缓存未命中可能是另一个处理器上的缓存命中,具体取决于每个处理器的设计细节以及操作系统。

有多种方法可以找到此类模式,常用的有两种。一种是使用gem5Sniper等模拟器。另一种是使用硬件性能事件。表示缓存未命中的事件可用,但它们不提供有关发生未命中的原因或位置的任何详细信息。但是,使用探查器,您可以将相应硬件性能事件报告的高速缓存未命中与导致这些未命中的指令大致关联起来,而这些指令又可以使用调试信息映射回源代码中的位置。此类分析器的示例包括Intel VTune AmplifierAMD CodeXL。模拟器和分析器产生的结果可能不准确,因此在解释它们时必须小心。