Oracle® Developer Studio 12.5:性能分析器教程

退出打印视图

更新时间: 2016 年 6 月
 
 

了解高速缓存争用和高速缓存分析度量

本节和教程的其余部分需要一个包含来自精确 dcm 硬件计数器的数据的实验。如果您的系统不支持精确 dcm 计数器,则本教程的剩余部分不适用于在系统上记录的实验。

dcm 计数器对高速缓存未命中次数进行计数,高速缓存未命中次数是引用不在高速缓存中的内存地址的装入和存储次数。

地址可能由于以下任一原因而不在高速缓存中:

  • 因为当前指令是对该 CPU 中该内存位置的首次引用。更准确地说,它是对共享高速缓存行的任何内存地址的首次引用。

  • 因为线程已引用如此多的其他内存地址,导致当前地址已从高速缓存刷新。这是容量未命中的情况。

  • 因为线程已引用映射到同一高速缓存行的其他内存地址,导致当前地址被刷新。这是冲突未命中的情况。

  • 因为其他线程已写入高速缓存行中的地址,导致当前线程的高速缓存行被刷新。这是共享未命中的情况,并可能是以下两种共享未命中之一:

    • 真共享,其中其他线程已写入当前线程所引用的同一地址。真共享导致的高速缓存未命中是无法避免的。

    • 伪共享,其中其他线程已写入与当前线程所引用的地址不同的地址。高速缓存未命中由于伪共享而发生,因为高速缓存硬件在高速缓存行粒度上运作,而不是在数据字粒度上运作。通过更改相关数据结构,以便在每个线程中引用的不同地址处于不同的高速缓存行上,可以避免伪共享。

以下过程检查对函数 computeB() 具有影响的伪共享情况。

  1. 返回到 "Overview"(概述),然后启用 "L1 D-cache Misses"(L1 数据高速缓存未命中次数)的度量,并禁用 "Cycles Per Instruction"(每个指令的周期数)的度量。

    image:启用了 “L1 D-cache Misses“(L1 数据高速缓存未命中次数)计数器的 “Overview“(概述)页面
  2. 切换回 "Functions"(函数)视图并查看 compute*() 例程。

    image:显示 “L1 D-cache Misses“(L1 数据高速缓存未命中次数)的度量的 computeB 函数视图

    回想一下,所有的 compute*() 函数都显示了大致相同的指令计数,而 computeB() 显示了更高的 "Total CPU Time"(CPU 总时间),并且是具有 "Exclusive L1 D-cache Misses"(独占 L1 数据高速缓存未命中次数)的重要计数的唯一函数。

  3. 返回到 "Source"(源)视图,请注意在 computeB() 中高速缓存未命中次数处于单行循环中。

  4. 如果尚未在导航面板中看到 "Disassembly"(反汇编)标签,要添加该视图,请单击导航面板顶部 "Views"(视图)标签旁边的 + 按钮,然后选中 "Disassembly"(反汇编)复选框。

    滚动 "Disassembly"(反汇编)视图,直到您看到 "L1 D-Cache Misses"(L1 数据高速缓存未命中次数)很高的装入指令对应的行。


    提示  -  视图(如 "Disassembly"(反汇编))的右边缘包括快捷方式,可以单击它们以跳转到具有高度量的行或热线。尝试单击边缘顶部的 "Next Hot Line"(下一个热线)向下箭头或 "Non-Zero Metrics"(非零度量)标记,以快速跳转到具有值得注意的度量值的行。
    image:computeB 函数的 “Disassembly“(反汇编)视图

    在 SPARC 系统上,如果使用 –xhwcprof 进行编译,则使用表明指令正在引用 workStruct_t 数据结构中的双字 sum_ctr 的结构信息对装入和存储进行注释。您还会看到其地址与下一行相同的行,它们将 <branch target> 作为其指令。这样的行表示下一个地址是分支的目标,这意味着代码可能已到达指示为热点的指令,而不曾执行 <branch target> 之上的指令。

    在 x86 系统上,不对装入和存储进行注释,也不显示 <branch target> 行,因为 x86 不支持 –xhwcprof

  5. 在 "Functions"(函数)和 "Disassembly"(反汇编)视图之间来回切换,选择各种 compute*() 函数。

    请注意,对于所有的 compute*() 函数,"Instructions Executed"(执行的指令)计数很高的指令将引用相同的结构字段。

现在您已看到,computeB() 所用的时间比其他函数长得多,即使它执行相同数量的指令也是如此,而且它是获取高速缓存未命中次数的唯一函数。高速缓存未命中次数是造成执行指令的周期数增加的原因,因为与具有高速缓存命中的装入相比,完成具有高速缓存未命中的装入所用的周期要多得多。

对于除 computeB() 之外的所有 compute*() 函数,结构 workStruct_t 中每个线程的参数所指向的双字字段 sum_ctr 包含在该线程的 Workblk 内。虽然 Workblk 结构是连续分配的,但是它们大到足以使每个结构中的双字距离太远而无法共享高速缓存行。

对于 computeB(),线程中的 workStruct_t 参数是该结构的连续实例,它的长度仅为一个双字 long。因此,由不同线程使用的双字将共享高速缓存行,这会导致一个线程中的任何存储使其他线程中的高速缓存行无效。这就是高速缓存未命中计数如此高的原因所在,重新填充高速缓存行的延迟是总 CPU 时间和 CPU 周期度量如此高的原因所在。

在此示例中,线程存储的数据字未重叠,尽管它们共享高速缓存行。此性能问题称为“伪共享”。如果线程引用相同的数据字,则将是真共享。到目前为止查看的数据并不区分伪共享和真共享。

在本教程的最后一节中,将说明伪共享和真共享之间的差异。