您如何从用户模式代码中一般性地检测缓存行关联性?

我正在为valgrind中的cachegrind / callgrind工具编写一个小补丁,它将使用完全通用的代码,CPU指令和缓存配置自动检测(现在只有x86 / x64自动配置,而其他架构不提供CPUID类型配置为非特权代码)。 此代码需要完全在非特权上下文中执行,即纯用户模式代码。 它还需要可以在非常不同的POSIX实现中移植,因此grokking / proc / cpuinfo不会这样做,因为我们的目标系统之一没有这样的东西。

检测CPU的频率,高速缓存的数量,它们的大小,甚至高速缓存行大小都可以使用100%通用POSIX代码完成,该代码没有任何特定于CPU的操作码(只是很多合理的假设,例如添加两个数字在一起,如果没有内存或寄存器依赖性停顿,可能会在一个周期内执行)。 这部分相当简单。

什么不是那么简单,为什么我问StackOverflow,是如何检测给定缓存的缓存行关联性? 关联性是缓存中可以包含来自主内存的给定缓存行的位数。 我可以看到可以检测到L1缓存关联,但L2缓存? 当然L1关联性会受到影响吗?

我很欣赏这可能是一个无法解决的问题。 但我把它扔到StackOverflow上,希望有人知道我不知道的事情。 请注意,如果我们在这里失败,我将简单地在四方的关联性默认值中进行硬编码,假设它不会对结果产生巨大影响。

谢谢,
尼尔

这是一个方案:

具有跨步S的存储器访问模式,并且访问的唯一元素的数量= N. 测试首先触摸每个唯一元素,然后通过非常多次访问相同模式来测量访问每个元素的平均时间。

示例:对于S = 2且N = 4,地址模式为0,2,4,6,0,2,4,6,0,2,4,6,…

考虑多级缓存层次结构。 您可以做出以下合理假设:

  • 第n + 1级高速缓存的大小是第n高速缓存大小的两倍
  • 第n + 1个高速缓存的关联性也是第n个高速缓存的关联性的两倍。

这两个假设允许我们说如果两个地址映射到第n + 1个缓存中的相同集合(比如L2),那么它们必须映射到第n个缓存中的相同集合(比如L1)。

假设你知道L1,L2缓存的大小。 您需要找到L2缓存的关联性。

  • 设置步幅S = L2缓存的大小(这样每个访问都映射到L2中的同一个集合,也在L1中)
  • 变化N (乘以2)

你得到以下制度:

  • 制度1: N <= L1的相关性。 (所有访问L1中的HIT)
  • 制度2: L1的相关性
  • 制度3: N> L2的相关性(L2中的所有访问未命中)

因此,如果您将平均访问时间与N进行绘制(当S = L2的大小时),您将看到一个类似阶梯的图。 最低步骤的结束为您提供L1的相关性。 下一步为您提供L2的相关性。

您可以在L2-L3和so-on之间重复相同的过程。 如果有帮助,请告诉我。 通过改变存储器访问模式的步幅来获得高速缓存参数的方法类似于LMBENCH基准测试所使用的方法。 我不知道lmbench是否也推断了关联性。

你能做一个只能访问同一套线的小程序吗? 然后,您可以增加访问之间的堆栈距离,并且当执行时间显着下降时,您可以假设您已达到关联性。

它可能不是很稳定,但也许可以带来领先,不知道。 我希望它可以提供帮助。

对于x86平台,您可以使用cpuid

有关详细信息,请参见http://www.intel.com/content/www/us/en/processors/processor-identification-cpuid-instruction-note.html 。

你需要这样的东西:

 long _eax,_ebx,_ecx,_edx; long op = func; asm ("cpuid" : "=a" (_eax), "=b" (_ebx), "=c" (_ecx), "=d" (_edx) : "a" (op) ); 

然后根据上面提到的链接中的文档使用信息。