您如何从用户模式代码中一般性地检测缓存行关联性?
我正在为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) );
然后根据上面提到的链接中的文档使用信息。