每当发生缓存未命中时,是否可以知道丢失的缓存行的地址?现代处理器中是否有可以提供此类信息的硬件性能计数器?
答案 0 :(得分: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);
}
编译:
g++ -g -O1 -march=native perf-mem-test.cpp -o perf-mem-test
并运行:
sudo perf mem record -U ./perf-mem-test && sudo perf mem report
生成按延迟排序的内存访问报告,如下所示:
Data Symbol
列显示了加载定位的地址 - 此处大多数显示为p+0xa0658b4
,这意味着距0xa0658b4
的{{1}}偏移p
这是有道理的,因为代码正在读写p
。该列表按&#34;本地重量排序&#34;这是参考周期 1 中的访问延迟。
请注意,记录的信息只是内存访问的样本:记录每次遗漏通常都会产生太多信息。此外,它默认只记录延迟为30个周期或更长的负载,但您可以使用命令行参数进行调整。
如果您只对所有级别缓存中的访问感兴趣,那么您正在寻找&#34;本地RAM命中&#34;线 2 。也许您可以将采样限制为仅缓存未命中 - 我非常确定英特尔内存采样内容支持这一点,并且我认为您可以告诉perf mem
仅查看未命中。
最后,请注意,我在-U
之后使用record
参数指示perf mem
仅记录用户空间事件。默认情况下,它将包含内核事件,这可能对您有用,也可能没用。对于示例程序,有许多内核事件与将p
数组从二进制文件复制到可写进程内存相关联。
请记住,我专门安排了我的程序,以便全局数组p
最终出现在已初始化的.data
部分(二进制文件大约为400 MB!),以便它显示在列表中的正确符号。绝大多数情况下,您的进程将访问动态分配或堆栈内存,这只会为您提供原始地址。是否可以将其映射回有意义的对象取决于您是否跟踪了足够的信息以使其成为可能。
1 我认为它在参考周期中,但我可能错了,内核可能已经将其转换为纳秒?
2 &#34; Local&#34;并且&#34;击中&#34;这里的部分是指我们点击连接到当前内核的RAM的事实,即我们没有进入与多插槽NUMA配置中的另一个套接字相关联的RAM。
答案 1 :(得分:1)
如果您想知道特定处理器上每个缓存未命中的确切虚拟或物理地址,那将非常困难,有时甚至是不可能的。但是你更有可能对昂贵的内存访问模式感兴趣;这些模式由于在一个或多个级别的缓存子系统中丢失而导致大的延迟。请注意,请务必记住,一个处理器上的缓存未命中可能是另一个处理器上的缓存命中,具体取决于每个处理器的设计细节,还取决于操作系统。
有几种方法可以找到这种模式,其中有两种是常用的。一种是使用gem5或Sniper等模拟器。另一种是使用硬件性能事件。表示缓存未命中的事件可用,但它们不提供有关未命中发生的原因或位置的任何详细信息。但是,使用分析器,您可以将相应硬件性能事件报告的缓存未命中与引起它们的指令近似关联,而这些指令又可以使用调试信息映射回源代码中的位置。此类分析器的示例包括Intel VTune Amplifier和AMD CodeXL。模拟器和分析器产生的结果可能不准确,因此在解释它们时必须小心。