现在的位置: 首页 > 综合 > 正文

评估 Linux on POWER 的性能

2017年11月24日 ⁄ 综合 ⁄ 共 20993字 ⁄ 字号 评论关闭

本文主要演示使用常用 Linux 工具显示可能的 CPU 停滞、流水线冲突和性能问题。并与文章最后一节中,分析并优化一个 POWER7 算法

Linux上的工具

您可以如何使用 POWER7 处理器中的 PCM?虽然您可以在 POWER 上使用各种分析方法,像硬件中断、代码仪表(如 gprof)和运营系统钩子 (systemtap);PCM 提供了直接使用处理器功能的一组广泛的计数器。PCM 分析仪利用操作系统中断持续定期地对处理器寄存器值进行采样。虽然采样分析的结果的数值准确度可能会比指令跟踪结果低一些,但它对系统的整体性能影响较小,并允许目标基准近全速地运行。由此产生的数据是不准确的,是一个有误差的近似值。

Linux 上最常用的两个 PCM 分析工具是 OProfileperf(参阅
参考资料
)。虽然两者都使用同样的原理,即沿工作负载的回溯(通过 syscall)不断对特殊的硬件寄存器进行采样,但它们分别采用不同的方式进行配置和使用。

OProfile 工具是面向 Linux 系统的全系统分析器,它能够以较低的开销分析所有运行的代码。它包括用于采集样本数据的一个内核驱动程序和守护程序、几个用来将数据转化成信息的后分析工具。调试符号(gcc 的
-g 选项)仅在需要注释源代码时才是必要的。利用最新的 Linux 2.6 内核,OProfile 可以提供 gprof 风格的 call-graph 分析信息。OProfile 的典型开销为 1-8%,具体取决于采样频率和工作负载。

在 POWER 上,OProfile 通过监视性能硬件计数器组和性能计数器完成其工作,但不同的组不能一起使用。这意味着,如果从相同的工作负载获得不同的性能计数器,则需要使用不同的
OProfile 事件配置多次运行 OProfile。这还意味着,您不能在同一时间监视整个 POWER7 CBM。可以在上述 “POWER7 PMY Detailed Event Description” 文档中定义可用的组,或运行

清单 1
中的命令定义它:

清单 1.OProfile组清单

				
# opcontrol -l

 

清单 2 中的命令演示了一个简单的
OProfile 配置和调用:

清单 2.OProfile POWER 7 CPU周期配置

				
# opcontrol -l
# opcontrol -–no-vmlinux
# opcontrol -e PM_CYC_GRP1:500000 -e PM_INST_CMPL_GRP1:500000 -e PM_RUN_CYC_GRP1:500000 
-e PM_RUN_INST_CMPL_GRP1:500000
# opcontrol --start

 

运行
清单 3
中的工作负载。

清单 3.OProfile运行命令顺序

				
# opcontrol --dump 
# opcontrol –-stop
# opcontrol --shutdown

 

要获得性能计数器报告,可以发出
清单 4
中的命令:

清单 4.OProfile报告生成

				
# opreport -l > workload_report

 

注:在 developerWorks 文章 "Identify performance bottlenecks with OProfile for Linux on POWER"(参阅

参考资料
)中可以找到 OProfile 的全面指南(尚未针对 POWER7 进行更新)。

Linux 内核 2.6.29 中引入了 perf 工具,该工具可以分析硬件和软件层面的性能事件。perf 工具拥有面向程序的优势,而不是像 OProfile 那样是面向系统的。它有一些预设的性能计数器列表,如 'cpu-cycles OR cycles'、'branch-misses' 或 'L1-icache-prefetch-misses',它能够复用 PMU 组,允许同一时间从不同的组收集多个性能计数器,但要以采样精度为代价。

有一个缺点是,虽然它支持直接收集硬件性能计数器,但 perf 不承认 POWER7 CBM 表示的计数器名称,需要使用原始的十六进制数字来代替它。表 1
OProfile 事件与十六进制数字之间的映射,可以与 perf(使用原始事件的记录选项)配合使用,以便利用 POWER7 的 CBM。

表 1.POWER 7 perf事件原始代码

计数器 原始代码
PM_RUN_CYC 200f4
PM_CMPLU_STALL 4000a
PM_CMPLU_STALL_FXU 20014
PM_CMPLU_STALL_DIV 40014
PM_CMPLU_STALL_SCALAR 40012
PM_CMPLU_STALL_SCALAR_LONG 20018
PM_CMPLU_STALL_VECTOR 2001c
PM_CMPLU_STALL_VECTOR_LONG 4004a
PM_CMPLU_STALL_LSU 20012
PM_CMPLU_STALL_REJECT 40016
PM_CMPLU_STALL_ERAT_MISS 40018
PM_CMPLU_STALL_DCACHE_MISS 20016
PM_CMPLU_STALL_STORE 2004a
PM_CMPLU_STALL_THRD 1001c
PM_CMPLU_STALL_IFU 4004c
PM_CMPLU_STALL_BRU 4004e
PM_GCT_NOSLOT_CYC 100f8
PM_GCT_NOSLOT_IC_MISS 2001a
PM_GCT_NOSLOT_BR_MPRED 4001a
PM_GCT_NOSLOT_BR_MPRED_IC_MISS 4001c
PM_GRP_CMPL 30004
PM_1PLUS_PPC_CMPL 100f2

:在 IBM Wiki "Using perf on POWER7 systems"(参阅
参考资料
)中,可以找到 perf 的全面指南(尚未针对 POWER7 进行更新)。

您可以获得与 perf 配合使用的原始代码,这些原始代码与 libpfm4 项目(参阅
参考资料
)的 OProfile 中定义的 POWER7 事件相对应:它们是在 POWER7 特定的头部 (lib/events/power7_events.h) 中定义的。示例程序
examples/showevtinfo 还显示了事件名称和相应的原始十六进制代码。

为了获得计数器信息,分析是一种常见的做法。分析使开发人员能够识别代码执行和数据访问的热点,找到性能敏感的领域,了解内存访问模式等。在开始分析之前,必须制定一个性能评估策略。该程序可能会由各种模块和/或动态共享对象 (DSO) 组成,它可能频繁地使用内核,也可能更多地依赖于数据模式访问(对 L2 或 L3 缓存访问有很大压力),或者侧重于矢量运算单位。下一节
会重点讨论可能的性能评估策略。

性能评估的策略

初始性能评估是通过检查 CPU 周期利用率计数器找到程序热点。要在 POWER7 上做到这一点,则需要监视
表 2
中列出的事件:

表 2 POWER 7 CPU周期利用率计数器

计数器 描述
PM_CYC Processor Cycles
PM_INST_CMPL 已完成的 PowerPC Instructions 数量
PM_RUN_CYC 可以通过运行锁存器得到控制的 Processor Cycles。操作系统使用运行锁存器来表示它们何时在做有益的工作。运行锁存器通常在 OS 的空闲循环中被清除。通过运行锁存器进行控制,可以过滤出空闲循环。
PM_RUN_INST_CMPL 已完成的运行指令数量

运行配有这些事件的 OProfile 将显示处理器在某个符号中花费的总时间。下面是一个使用 IBM Advance Toolchain 5.0 for POWER(参阅

参考资料
)编译的 SPECcpu2006 基准套件的 403.gcc 组件的示例概要文件输出。以下是 opreport -l 命令的输出。

清单 5

				
CPU: ppc64 POWER7, speed 3550 MHz (estimated) 
Counted PM_CYC_GRP1 events ((Group 1 pm_utilization) Processor Cycles) with a unit 
mask of 0x00 (No unit mask) count 500000 
Counted PM_INST_CMPL_GRP1 events ((Group 1 pm_utilization) Number of PowerPC 
Instructions that completed.) with a unit mask of 0x00 (No unit mask) count 500000 

samples  %        samples  %        image name      app name        symbol name 
204528    7.9112  32132     1.3848  gcc_base.none   gcc_base.none   reg_is_remote_cons\
                                                                    tant_p.isra.3.part.4 
125218    4.8434  246710   10.6324  gcc_base.none   gcc_base.none   bitmap_operation 
113190    4.3782  50950     2.1958  libc-2.13.so    libc-2.13.so    memset 
90316     3.4934  22193     0.9564  gcc_base.none   gcc_base.none   compute_transp 
89978     3.4804  11753     0.5065  vmlinux         vmlinux         .pseries_dedicated_\
                                                                    idle_sleep 
88429     3.4204  130166    5.6097  gcc_base.none   gcc_base.none   bitmap_element_\
                                                                    allocate 
67720     2.6194  41479     1.7876  gcc_base.none   gcc_base.none   ggc_set_mark 
56613     2.1898  89418     3.8536  gcc_base.none   gcc_base.none   canon_rtx 
53949     2.0868  6985      0.3010  gcc_base.none   gcc_base.none   delete_null_\
                                                                    pointer_checks 
51587     1.9954  26000     1.1205  gcc_base.none   gcc_base.none   ggc_mark_rtx_\
                                                                    children_1 
48050     1.8586  16086     0.6933  gcc_base.none   gcc_base.none   single_set_2 
47115     1.8224  33772     1.4555  gcc_base.none   gcc_base.none   note_stores 

清单 6

				
Counted PM_RUN_CYC_GRP1 events ((Group 1 pm_utilization) Processor Cycles gated by the 
run latch.  Operating systems use the run latch to indicate when they are doing useful 
work.  The run 
latch is typically cleared in the OS idle loop.  Gating by the run latch filters out 
the idle loop.) with a unit mask of 0x00 (No unit mask) count 500000 
Counted PM_RUN_INST_CMPL_GRP1 events ((Group 1 pm_utilization) Number of run 
instructions completed.) with a unit mask of 0x00 (No unit mask) count 500000 

samples  %        samples  %        samples  %      app name        symbol name 
204538    8.3658  32078     1.3965  gcc_base.none   gcc_base.none   reg_is_remote_consta\
                                                                    nt_p.isra.3.part.4 
124596    5.0961  252227   10.9809  gcc_base.none   gcc_base.none   bitmap_operation 
112326    4.5943  50890     2.2155  libc-2.13.so    libc-2.13.so    memset 
90312     3.6939  21882     0.9527  gcc_base.none   gcc_base.none   compute_transp 
0              0  0              0  vmlinux         vmlinux         .pseries_dedicated\
                                                                    _idle_sleep 
88894     3.6359  124831    5.4346  gcc_base.none   gcc_base.none   bitmap_element_all\
                                                                    ocate 
67995     2.7811  41331     1.7994  gcc_base.none   gcc_base.none   ggc_set_mark
56460     2.3093  89484     3.8958  gcc_base.none   gcc_base.none   canon_rtx
54076     2.2118  6965      0.3032  gcc_base.none   gcc_base.none   delete_null_pointer\
                                                                    _checks
51228     2.0953  26057     1.1344  gcc_base.none   gcc_base.none   ggc_mark_rtx_childr\
                                                                    en_1 
48057     1.9656  16005     0.6968  gcc_base.none   gcc_base.none   single_set_2 
47160     1.9289  33766     1.4700  gcc_base.none   gcc_base.none   note_stores
 

 

每个被监视的事件在输出中都是用一对列来表示的。第一列显示针对指定事件从 PCM 收集的样品数量,第二列显示它所表示的样本总数的百分比。正如在该报告所见,符号
reg_is_remote_constant_p 消耗了最多的处理器周期,并且是代码优化的一个很好的候选。此概要文件仅标识哪个符号消耗了最多的 CPU 周期,没有标识处理器流水线是否被充分利用。您可以通过比较计数器的结果,查看流水线的利用率。

查看计数器 PM_INST_CMPL_GRP1(第二对列);符号 bitmap_operation 显示出了比符号 reg_is_remote_constant_p 更高的百分比。完成每个处理器指令的时候,此性能计数器都会递增,而
PM_CYC_GRP1 仅意味着已被利用的 CPU 周期数量。如果没有进一步的分析,这可能表示符号 reg_is_remote_constant_p 包含比符号 bitmap_operation 更多的 CPU 停滞,因为为符号
reg_is_remote_constant_p 完成的指令数量明显更少一些。此概要文件提供了一个有关后续优化工作应该集中在哪个符号上的初始提示。

在您开始挖掘和破解代码之前,应该先了解工作负载是 CPU 密集型的还是内存密集型的,这样做明智之举。这很重要,因为每种工作负载类型的优化方法都非常不同。例如,最常见的内存访问来自缓存或主内存(与 NUMA 远程节点内存访问相反),性能几乎完全依赖于所使用的算法和数据结构。要调查内存访问模式,可以监视

表 3
中的以下两个性能计数器:

表 3.POWER 内存利用率计数器

计数器 描述
PM_MEM0_RQ_DISP 读取为主内存分派的请求
PM_MEM0_WQ_DISP 写入为主内存分派的请求

这两个计数器可以指示内存访问模式主要来自内存读取、写入,还是两者兼而有之。使用和以前一样的基准(来自 SPECcpu2006 的 403.gcc),概要文件显示如下:

清单 7

				
CPU: ppc64 POWER7, speed 3550 MHz (estimated) 
Counted PM_MEM0_RQ_DISP_GRP59 events ((Group 59 pm_nest2)  Nest events (MC0/MC1/PB/GX), 
Pair0 Bit1) with a unit mask of 0x00 (No unit mask) count 1000 
Counted PM_MEM0_WQ_DISP_GRP59 events ((Group 59 pm_nest2)  Nest events (MC0/MC1/PB/GX), 
Pair3 Bit1) with a unit mask of 0x00 (No unit mask) count 1000 
samples  %        samples  %        app name                 symbol name 
225841   25.8000  289       0.4086  gcc_base.none            reg_is_remote_constant_p.\
                                                             isra.3.part.4 
90068    10.2893  2183      3.0862  gcc_base.none            compute_transp 
54038     6.1733  308       0.4354  gcc_base.none            single_set_2 
32660     3.7311  2006      2.8359  gcc_base.none            delete_null_pointer_checks 
26352     3.0104  1498      2.1178  gcc_base.none            note_stores 
21306     2.4340  1950      2.7568  vmlinux                  .pseries_dedicated_idle_sl\
                                                             eep 
18059     2.0631  9186     12.9865  libc-2.13.so             memset 
15867     1.8126  659       0.9316  gcc_base.none            init_alias_analysis

 

要观察的另一组有趣的性能计数器是缓存(L2 和 L3)上的访问压力。下面的示例使用了 perf 来分析使用 RHEL6.2 Linux system GCC 构建的 SPECcpu2006 483.xalancbmk 组件(参阅

参考资料
)。此组件大量使用了内存分配例程,因此预计内存子系统的压力会很大。为了分析压力,可以利用 OProfile 监视
表 4
中的下列计数器:

表 4

计数器 描述
PM_DATA_FROM_L2 处理器的 Data Cache 是因为需求负载从本地 L2 重新加载的
PM_DATA_FROM_L3 处理器的 Data Cache 是因为需求负载从本地 L3 重新加载的
PM_DATA_FROM_LMEM 处理器的 Data Cache 是从附加在该处理器所在的模块的内存进行重新加载的
PM_DATA_FROM_RMEM 处理器的 Data Cache 是因为需求负载从附加在与处理器所处模块不同的另一个模块的内存重新加载的

概要文件输出显示如下:

清单 8. 489.Xalancbmk 基准的 'opreport -' 输出(计数器 PM_DATA_FROM_L2_GRP91 和 PM_DATA_FROM_L3_GRP91)

				
CPU: ppc64 POWER7, speed 3550 MHz (estimated) 
Counted PM_DATA_FROM_L2_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache
was reloaded from the local L2 due to a demand load.) with a unit mask of 0x00 (No unit
 mask) count 1000 
Counted PM_DATA_FROM_L3_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache
 was reloaded from the local L3 due to a demand load.) with a unit mask of 0x00 (No unit
 mask) count 1000 
samples  %        samples  %        image name     app name       symbol name 
767827   25.5750  7581      0.2525  gcc_base.none  gcc_base.none  bitmap_element_allocate
377138   12.5618  8341      0.2778  gcc_base.none  gcc_base.none  bitmap_operation 
93334     3.1088  3160      0.1052  gcc_base.none  gcc_base.none  bitmap_bit_p 
70278     2.3408  5913      0.1969  libc-2.13.so   libc-2.13.so   _int_free 
56851     1.8936  22874     0.7618  oprofile       oprofile       /oprofile 
47570     1.5845  2881      0.0959  gcc_base.none  gcc_base.none  rehash_using_reg 
41441     1.3803  8532      0.2841  libc-2.13.so   libc-2.13.so   _int_malloc

清单 9

				
Counted PM_DATA_FROM_LMEM_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache
was reloaded from memory attached to the same module this proccessor is located on.) with
 a unit mask of 0x00 (No unit mask) count 1000 
Counted PM_DATA_FROM_RMEM_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache
 was reloaded from memory attached to a different module than this proccessor is located 
on.) with a unit mask of 0x00 (No unit mask) count 1000
samples  %        samples  %        image name     app name       symbol name 
1605      0.3344  0              0  gcc_base.none  gcc_base.none  bitmap_element_allocate
1778      0.3704  0              0  gcc_base.none  gcc_base.none  bitmap_operation 
1231      0.2564  0              0  gcc_base.none  gcc_base.none  bitmap_bit_p 
205       0.0427  0              0  libc-2.13.so   libc-2.13.so   _int_free 
583       0.1215  327      100.000  oprofile       oprofile       /oprofile 
0              0  0              0  gcc_base.none  gcc_base.none  rehash_using_reg 
225       0.0469  0              0  libc-2.13.so   libc-2.13.so   _int_malloc

 

解释概要文件输出显示,大多数缓存压力来自 L2 访问,几乎不需求从 L3 重载,因为 L2 访问 (PM_DATA_FROM_L2) 的计数器采样值的总数和相对值都比 L3 需求重载 (PM_DATA_FROM_L3) 要高一些。通过进行更全面的分析(通过监视更多计数器),您只能获得进一步的信息,像 L2 访问是否由于缓存未命中而造成 CPU 停滞。从该示例概要文件可以得出的结论是,与缓存访问相比,主内存访问(PM_DATA_FROM_LMEM 事件)相当低,并且没有远程访问(事件
PM_DATA_FROM_RMEM),这意味着没有远程 NUMA 节点内存访问。热点和内存访问模式的分析可以提供优化工作的方向;在本例中,需要进一步进行分析,确定究竟是什么造成 CPU 停顿,因为对于正确识别 CPU 停顿而言,只是简单识别工作负载热点和内存访问模式是远远不够的。

为了制定更好的性能优化策略,需要使用 perf 工具而不是 OProfile 来做进一步的分析,因为需要同时监视很多 POWER7 CMB 计数器,所以

图 3
中显示了 22 个计数器,以便为性能优化提供更好的策略。许多事件都位于不同的组中,这意味着使用 OProfile 需要多次运行相同的工作负载。若指定的计数器位于多个组中,那么
perf 工具将会复用硬件计数器的监视。虽然这会产生一个准确度稍低的结果,但整体结果往往与预期非常近似,并且具有可以在更短时间内完成分析的优势。

下面的示例使用 perf 分析同一个 SPECcpu2006 483.xalancbmk 组件。要分析这个组件,可以发出

清单 10
的命令:

清单 10

				
$ /usr/bin/perf stat -C 0 -e r100f2,r4001a,r100f8,r4001c,r2001a,r200f4,r2004a,r4004a,
r4004e,r4004c,r20016,r40018,r20012,r40016,r40012,r20018,r4000a,r2001c,r1001c,r20014,
r40014,r30004 taskset -c 0 ./Xalan_base.none -v t5.xml xalanc.xsl > power7_cbm.dat

 

此命令会导致 perf 监视通过 -c 指定的 CPU 上的 -e 参数所定义的原始事件。任务集调用确保该组件将只在 CPU 0 上运行。工作负载
./Xalan_base.none -v t5.xml xalanc.xsl 可以被另一个应用程序替代为概要文件。完成概要文件的处理之后,perf 命令会输出一个简单的表,显示每个原始事件的总数,以及所花费的总秒数:

清单 11

				
 Performance counter stats for 'taskset -c 0 ./Xalan_base.none -v t5.xml xalanc.xsl': 


   366,860,486,404 r100f2                                                       [18.15%] 
     8,090,500,758 r4001a                                                       [13.65%] 
    50,655,176,004 r100f8                                                       [ 9.13%] 
    11,358,043,420 r4001c                                                       [ 9.11%] 
    10,318,533,758 r2001a                                                       [13.68%] 
 1,301,183,175,870 r200f4                                                       [18.22%] 
     2,150,935,303 r2004a                                                       [ 9.10%] 
                 0 r4004a                                                       [13.65%] 
   211,224,577,427 r4004e                                                       [ 4.54%] 
   212,033,138,844 r4004c                                                       [ 4.54%] 
   264,721,636,705 r20016                                                       [ 9.09%] 
    22,176,093,590 r40018                                                       [ 9.11%] 
   510,728,741,936 r20012                                                       [ 9.10%] 
    39,823,575,049 r40016                                                       [ 9.07%] 
     7,219,335,816 r40012                                                       [ 4.54%] 
         1,585,358 r20018                                                       [ 9.08%] 
   882,639,601,431 r4000a                                                       [ 9.08%] 
     1,219,039,175 r2001c                                                       [ 9.08%] 
         3,107,304 r1001c                                                       [13.62%] 
   120,319,547,023 r20014                                                       [ 9.09%] 
    50,684,413,751 r40014                                                       [13.62%] 
   366,940,826,307 r30004                                                       [18.16%] 

     461.057870036 seconds time elapsed 

 

为了分析针对 POWER7 CBM 的 perf 输出,我们提供了一个 Python 脚本(在
下载
中检查 power7_cbm.zip),该脚本由收集到的虚拟和硬件计数器的计数器指标组成。要创建一个报告,可以发出
清单 12
中的命令:

清单 12

				
$ power7_cbm.py power7_cbm.dat

 

类似于
清单 13
的输出将被打印出来:

清单 13

				
CPI Breakdown Model (Complete) 

Metric                         :            Value :    Percent 
PM_CMPLU_STALL_DIV             :    49802421337.0 :        0.0 
PM_CMPLU_STALL_FXU_OTHER       :    67578558649.0 :        5.2 
PM_CMPLU_STALL_SCALAR_LONG     :        2011413.0 :        0.0 
PM_CMPLU_STALL_SCALAR_OTHER    :     7195240404.0 :        0.6 
PM_CMPLU_STALL_VECTOR_LONG     :              0.0 :        0.0 
PM_CMPLU_STALL_VECTOR_OTHER    :     1209603592.0 :        0.1 
PM_CMPLU_STALL_ERAT_MISS       :    22193968056.0 :        1.7 
PM_CMPLU_STALL_REJECT_OTHER    :    18190293594.0 :        1.4 
PM_CMPLU_STALL_DCACHE_MISS     :   261865838255.0 :       20.3 
PM_CMPLU_STALL_STORE           :     2001544985.0 :        0.2 
PM_CMPLU_STALL_LSU_OTHER       :   202313206181.0 :       15.7 
PM_CMPLU_STALL_THRD            :        2025705.0 :        0.0 
PM_CMPLU_STALL_BRU             :   208356542821.0 :       16.2 
PM_CMPLU_STALL_IFU_OTHER       :     2171796336.0 :        0.2 
PM_CMPLU_STALL_OTHER           :    30895294057.0 :        2.4 
PM_GCT_NOSLOT_IC_MISS          :     9805421042.0 :        0.8 
PM_GCT_NOSLOT_BR_MPRED         :     7823508357.0 :        0.6 
PM_GCT_NOSLOT_BR_MPRED_IC_MISS :    11059314150.0 :        0.9 
PM_GCT_EMPTY_OTHER             :    20292049774.0 :        1.6 
PM_1PLUS_PPC_CMPL              :   365158978504.0 :       28.3 
OVERHEAD_EXPANSION             :      590057044.0 :        0.0 
Total                                             :       96.1

 

这份报告以误差范围内的统计值为基础,所以最终的比例并不完全准确。即使误差范围较大,CPU 总停滞中仍然只有大约 20% 是由于数据缓存未命中 (PM_CMPLU_STALL_DCACHE_MISS) 引起的。最后的指令完成百分比 (PM_1PLUS_PPC_CMPL) 也有 28% 左右。

未来的优化应尝试通过降低 CPU 停滞和/或 GCT (Global Completion Table) 百分比将该数字最大化。根据这份报告,另一个分析途径是识别发生停滞的代码。要做到这一点,可以使用
perf record 命令。该命令将追踪原始计数器的性能,并利用进程回溯来创建一个映射,从而识别哪个符号生成最多的硬件事件。这类似于
OProfile
的工作方式。在本例中,要跟踪 PM_CMPLU_STALL_DCACHE_MISS 事件,可以发出
清单 14
中的命令:

清单 14

				
$ /usr/bin/perf record -C 0 -e r20016 taskset -c 0 ./Xalan_base.none -v t5.xml xalanc.xsl

 

perf 命令将利用结果创建一个数据文件(通常是 "perf.dat")。使用 perf record 命令能够以交互方式读取它,如

清单 15
所示:

清单 15

				
Events: 192  raw 0x20016
    39.58%  Xalan_base.none  Xalan_base.none  [.] xercesc_2_5::ValueStore::contains 
    11.46%  Xalan_base.none  Xalan_base.none  [.] xalanc_1_8::XStringCachedAllocator
     9.90%  Xalan_base.none  Xalan_base.none  [.] xalanc_1_8::XStringCachedAllocator
     7.29%  Xalan_base.none  Xalan_base.none  [.] xercesc_2_5::ValueStore::isDuplica
     5.21%  Xalan_base.none  libc-2.13.so     [.] _int_malloc 
     5.21%  Xalan_base.none  Xalan_base.none  [.] __gnu_cxx::__normal_iterator<xa
     4.17%  Xalan_base.none  libc-2.13.so     [.] __GI___libc_malloc 
     2.08%  Xalan_base.none  libc-2.13.so     [.] malloc_consolidate.part.4 
     1.56%  Xalan_base.none  Xalan_base.none  [.] xalanc_1_8::ReusableArenaBlock<xa
     1.56%  Xalan_base.none  Xalan_base.none  [.] xalanc_1_8::ReusableArenaBlock<xa
     1.04%  Xalan_base.none  libc-2.13.so     [.] __free
[...]

 

凭借使用 POWER7 CBM 计数器和 perf report 工具得到的分析,您的优化工作可能集中于优化符号
xercesc_2_5::ValueStore::contains(xercesc_2_5::FieldValueMap const*)
上的内存和缓存访问。

这个示例仅仅是可能的分析的一个子集。POWER7 CBM 显示出,虽然数据缓存停滞是引起 CPU 停滞的主要原因,但加载和存储单元 (PM_CMPLU_STALL_LSU) 与分支单元 (PM_CMPLU_STALL_BRU) 都是停滞的原因。进一步分析可以解决这些计数器。

案例研究

以下案例研究将应用这些性能评估策略来分析一个三角函数数学函数实现。可以根据分析结果来确定优化机会。本案例研究中使用的函数是 ISO C hypot 函数,被定义为直角三角形的斜边长度。该函数被 C99, POSIX.1-2001 定义为:

double hypot(double x, double y);

hypot() 函数返回 sqrt(x*x+y*y)。 如果成功,该函数将返回一个直角三角形,两个直角边的长度分别是 x 和 y。如果 x 或 y 是无穷大,则返回正无穷大。如果 x 或 y 为 NaN,而另一个参数不是无穷大,则返回 NaN。如果结果溢出,就会发生一个范围错误,该函数会分别返回 HUGE_VAL、HUGE_VALF 或 HUGE_VALL。如果两个参数都异常,结果也是异常的,并且发现范围错误,则返回正确的结果。

该算法虽然看似简单,能够处理 Infinity 和 NaN 的 Floating-Point (FP) 参数,以及与 FP 有关的溢出/下溢,但它对性能影响增加了一些挑战。GNU C Library(参阅

参考资料
)提供一个位于 sysdeps/ieee754/dbl-64/e_hypot.c 源树中的 hypot 实现:

附录 中包含该代码示例的许可信息。

清单 16

				
double __ieee754_hypot(double x, double y) 
{ 
        double a,b,t1,t2,y1,y2,w; 
        int32_t j,k,ha,hb; 

        GET_HIGH_WORD(ha,x); 
        ha &= 0x7fffffff; 
        GET_HIGH_WORD(hb,y); 
        hb &= 0x7fffffff; 
        if(hb > ha) {a=y;b=x;j=ha; ha=hb;hb=j;} else {a=x;b=y;} 
        SET_HIGH_WORD(a,ha);    /* a <- |a| */ 
        SET_HIGH_WORD(b,hb);    /* b <- |b| */ 
        if((ha-hb)>0x3c00000) {return a+b;} /* x/y > 2**60 */ 
        k=0; 
        if(ha > 0x5f300000) {   /* a>2**500 */ 
           if(ha >= 0x7ff00000) {       /* Inf or NaN */ 
               u_int32_t low; 
               w = a+b;                 /* for sNaN */ 
               GET_LOW_WORD(low,a); 
               if(((ha&0xfffff)|low)==0) w = a; 
               GET_LOW_WORD(low,b); 
               if(((hb^0x7ff00000)|low)==0) w = b; 
               return w; 
           } 
           /* scale a and b by 2**-600 */ 
           ha -= 0x25800000; hb -= 0x25800000;  k += 600; 
           SET_HIGH_WORD(a,ha); 
           SET_HIGH_WORD(b,hb); 
        } 
        if(hb < 0x20b00000) {   /* b < 2**-500 */ 
            if(hb <= 0x000fffff) {      /* subnormal b or 0 */ 
                u_int32_t low; 
                GET_LOW_WORD(low,b); 
                if((hb|low)==0) return a; 
                t1=0; 
                SET_HIGH_WORD(t1,0x7fd00000);   /* t1=2^1022 */ 
                b *= t1; 
                a *= t1; 
                k -= 1022; 
            } else {            /* scale a and b by 2^600 */ 
                ha += 0x25800000;       /* a *= 2^600 */ 
                hb += 0x25800000;       /* b *= 2^600 */ 
                k -= 600; 
                SET_HIGH_WORD(a,ha); 
                SET_HIGH_WORD(b,hb); 
            } 
        } 
    /* medium size a and b */ 
        w = a-b; 
        if (w>b) { 
            t1 = 0; 
            SET_HIGH_WORD(t1,ha); 
            t2 = a-t1; 
            w  = __ieee754_sqrt(t1*t1-(b*(-b)-t2*(a+t1))); 
        } else { 
            a  = a+a; 
            y1 = 0; 
            SET_HIGH_WORD(y1,hb); 
            y2 = b - y1; 
            t1 = 0; 
            SET_HIGH_WORD(t1,ha+0x00100000); 
            t2 = a - t1; 
            w  = __ieee754_sqrt(t1*y1-(w*(-w)-(t1*y2+t2*b))); 
        } 
        if(k!=0) { 
            u_int32_t high; 
            t1 = 1.0; 
            GET_HIGH_WORD(high,t1); 
            SET_HIGH_WORD(t1,high+(k<<20)); 
            return t1*w; 
        } else return w; 
} 

 

这个实现相当复杂,主要是因为算法执行了许多 FP 到 INT 的逐位转换。它假定某些 FP 操作(如比较和乘法)在使用浮点数指令时比使用定点指令更昂贵。在某些架构上存在这种情况,但在 Power Architecture 上则不然。

评估该实现的第一步是创建一个可以分析的基准。在这种情况下,因为该实现仅仅是一个带有两个参数和一个简单算法的函数(没有内部函数调用或其他路径),所以可以建立一个简单的基准来评估它(查看

下载
中的 hypot_bench.tar.gz)。基准是性能评估的一部分;优化应该加快算法的运行,或者加快利用总工作负载性能的算法关键部分的运行。合成基准(像这个基准)应该代表该函数的正常使用。由于优化工作往往需要消耗大量资源和时间,所以您需要将重点放在最常见的情况或预期行为上。如果试图优化的代码所代表的程序总体使用率较低,那么这往往是一种资源浪费。

由于这是针对单个函数的性能分析,所以您可以跳过热点分析,将重点放在对 CBM 的分析上。使用 hypot_bench.c 中的基准与
perf
,CBM 信息如
清单 17
所示:

清单 17

				
CPI Breakdown Model (Complete) 

Metric                         :            Value :    Percent 
PM_CMPLU_STALL_DIV             :        8921688.0 :        8.7 
PM_CMPLU_STALL_FXU_OTHER       :    13953382275.0 :        5.0 
PM_CMPLU_STALL_SCALAR_LONG     :    24380128688.0 :        8.7 
PM_CMPLU_STALL_SCALAR_OTHER    :    33862492798.0 :       12.0 
PM_CMPLU_STALL_VECTOR_LONG     :              0.0 :        0.0 
PM_CMPLU_STALL_VECTOR_OTHER    :      275057010.0 :        0.1 
PM_CMPLU_STALL_ERAT_MISS       :         173439.0 :        0.0 
PM_CMPLU_STALL_REJECT_OTHER    :         902838.0 :        0.0 
PM_CMPLU_STALL_DCACHE_MISS     :       15200163.0 :        0.0 
PM_CMPLU_STALL_STORE           :        1837414.0 :        0.0 
PM_CMPLU_STALL_LSU_OTHER       :    94866270200.0 :       33.7 
PM_CMPLU_STALL_THRD            :         569036.0 :        0.0 
PM_CMPLU_STALL_BRU             :    10470012464.0 :        3.7 
PM_CMPLU_STALL_IFU_OTHER       :      -73357562.0 :        0.0 
PM_CMPLU_STALL_OTHER           :     7140295432.0 :        2.5 
PM_GCT_NOSLOT_IC_MISS          :        3586554.0 :        0.0 
PM_GCT_NOSLOT_BR_MPRED         :     1008950510.0 :        0.4 
PM_GCT_NOSLOT_BR_MPRED_IC_MISS :         795943.0 :        0.0 
PM_GCT_EMPTY_OTHER             :    42488384303.0 :       15.1 
PM_1PLUS_PPC_CMPL              :    53138626513.0 :       18.9 
OVERHEAD_EXPANSION             :       30852715.0 :        0.0 
Total                                             :      108.7

 

分析表明,大多数 CPU 停滞以及因此产生的性能损失都来自 Load and Store Unit(LSU - 计数器 PM_CMPLU_STALL_LSU_OTHER)。LSU 有多个计数器与之关联,然而,在 CPU 停滞分析的过程中,重点是与性能下降有关的计数器。在 POWER 上显示出性能下降的那些计数器与 Load-Hit-Store (LHS) 危害相关。当 CPU 将数据写入一个地址,然后太快尝试再次加载该数据时,就会出现这一个大型停滞。下一步是检查这个特定的算法中是否发生这种停滞,首先应该检查事件
PM_LSU_REJECT_LHS(原始代码 “rc8ac”),如
清单 18
所示。

清单 18

				
$ perf record -C 0 -e rc8ac taskset -c 0 ./hypot_bench_glibc
$ perf report
Events: 14K raw 0xc8ac
    79.19%  hypot_bench_gli  libm-2.12.so       [.] __ieee754_hypot
    10.38%  hypot_bench_gli  libm-2.12.so       [.] __hypot
     6.34%  hypot_bench_gli  libm-2.12.so       [.] __GI___finite

 

概要文件的输出显示,符号 __ieee754_hypot 生成了最多的 PM_LSU_REJECT_LHS 事件。调查编译器所产生的程序集代码,确定哪个指令生成了该事件。展开符号
__ieee754_hypot 以注释程序集,遍历 perf report 屏幕并选择 __ieee754_hypot 符号,如

清单 19
中的输出所示。

清单 19

				
         :        00000080fc38b730 <.__ieee754_hypot>:
    0.00 :          80fc38b730:   7c 08 02 a6     mflr    r0
    0.00 :          80fc38b734:   fb c1 ff f0     std     r30,-16(r1)
    0.00 :          80fc38b738:   fb e1 ff f8     std     r31,-8(r1)
   13.62 :          80fc38b73c:   f8 01 00 10     std     r0,16(r1)
    0.00 :          80fc38b740:   f8 21 ff 71     stdu    r1,-144(r1)
   10.82 :          80fc38b744:   d8 21 00 70     stfd    f1,112(r1)
    0.23 :          80fc38b748:   e9 21 00 70     ld      r9,112(r1)
   17.54 :          80fc38b74c:   d8 41 00 70     stfd    f2,112(r1)
    0.00 :          80fc38b750:   79 29 00 62     rldicl  r9,r9,32,33
    0.00 :          80fc38b754:   e9 61 00 70     ld      r11,112(r1)
    0.00 :          80fc38b758:   e8 01 00 70     ld      r0,112(r1)
    8.46 :          80fc38b75c:   d8 21 00 70     stfd    f1,112(r1)
[...]

 

在代码的前面部分,已使用宏 GET_HIGH_WORD 将一个 float 转换到 integer,以便实现后面的逐位操作。GLIBC 的 math/math_private.h 使用

清单 20
中的代码来定义宏。

清单 20

				
#define GET_HIGH_WORD(i,d)                                      \
do {                                                            \
  ieee_double_shape_type gh_u;                                  \
  gh_u.value = (d);                                             \
  (i) = gh_u.parts.msw;                                         \
} while (0)

 

在这个宏中,造成 LHS 停滞的罪魁祸首可能是将 float 的属性读取到了内部 value,然后将它读取到变量 i 的操作。POWER7 处理器没有将浮点寄存器的内容逐位转移到定点寄存器的原生指令。在 POWER 上实现该操作的方式是,使用一个存储操作将浮点寄存器中的 FP 数字存储到内存,然后将相同的内存位置加载到一个定点寄存器(通用)。由于内存访问比寄存器操作慢(即使在访问 L1 数据缓存时也如此),在存储过程中,CPU 会停滞,以便完成后续的加载。

:文档 "POWER ISA 2.06 (POWER7)"(参阅
参考资料
)包含更多的信息。

最常见的性能计数器事件触发中断,保留一个与执行指令接近的指令 PC 地址。这可能会导致不完全准确的程序集注释。为了缓解这种行为 POWER4,并在以后有一组有限的名称为
marked 的性能计数器。标记的指令在每个时间框架内将产生较少的事件;然而,PC 指令会是准确的,导致准确的程序集注释。标记的事件在
opcontrol -l
获得的 OProfile 计数器列表中带有 PM_MRK 前缀。

仔细检查分析,查看 PM_MRK_LSU_REJECT_LHS 计数器。PM_MRK_LSU_REJECT_LHS
PM_LSU_REJECT_LHS
这两个计数器监视相同的性能事件。不过,标记的计数器 (PM_MRK_LSU_REJECT_LHS) 在每个时间框架内会产生较少的事件,但带有更准确的程序集注释。(参见

清单 21
。)

清单21

				
$ perf record -C 0 -e rd082 taskset -c 0 ./hypot_bench_glibc
$ perf report
Events: 256K raw 0xd082
    64.61%  hypot_bench_gli  libm-2.12.so       [.] __ieee754_hypot
    35.33%  hypot_bench_gli  libm-2.12.so       [.] __GI___finite

 

这会生成
清单 22
中的程序集注释。

清单 22

				
         :        00000080fc38b730 <.__ieee754_hypot>:
[...]
    1.23 :          80fc38b7a8:   c9 a1 00 70     lfd     f13,112(r1)
    0.00 :          80fc38b7ac:   f8 01 00 70     std     r0,112(r1)
   32.66 :          80fc38b7b0:   c8 01 00 70     lfd     f0,112(r1)
[...]
    0.00 :          80fc38b954:   f8 01 00 70     std     r0,112(r1)
    0.00 :          80fc38b958:   e8 0b 00 00     ld      r0,0(r11)
    0.00 :          80fc38b95c:   79 00 00 0e     rldimi  r0,r8,32,0
   61.72 :          80fc38b960:   c9 61 00 70     lfd     f11,112(r1
[...]

 

另一个符号显示大约有 35% 的生成事件有类似的行为,如
清单 23
所示。

清单 23

				
         :        00000080fc3a2610 <.__finitel>>
    0.00 :          80fc3a2610:   d8 21 ff f0     stfd    f1,-16(r1)
  100.00 :          80fc3a2614:   e8 01 ff f0     ld      r0,-16(r1)

 

根据这些信息,优化工作可能是,通过删除 FP 到 INT 的转换,消除这些停滞。POWER 处理器具有快速、高效的 Float-Point 执行单元,所以不需要使用 Fixed-Point 指令来执行这些计算。POWER 目前在 GLIBC 中使用的算法 (sysdeps/powerpc/fpu/e_hypot.c) 只使用 FP 操作就可以删除所有 LHS 停滞。结果是获得了更简单的算法,如

清单 24
所示。

清单 24

				
double
__ieee754_hypot (double x, double y)
{
  x = fabs (x);
  y = fabs (y);

  TEST_INF_NAN (x, y);

  if (y > x)
    {
      double t = x;
      x = y;
      y = t;
    }
  if (y == 0.0 || (x / y) > two60)
    {
      return x + y;
    }
  if (x > two500)
    {
      x *= twoM600;
      y *= twoM600;
      return __ieee754_sqrt (x * x + y * y) / twoM600;
    }
  if (y < twoM500)
    {
      if (y <= pdnum)
        {
          x *= two1022;
          y *= two1022;
          return __ieee754_sqrt (x * x + y * y) / two1022;
        }
      else
        {
          x *= two600;
          y *= two600;
          return __ieee754_sqrt (x * x + y * y) / two600;
        }
    }
  return __ieee754_sqrt (x * x + y * y);
}

 

TEST_INF_NAN 宏是更进一步的小优化,在开始更进一步的 FP 操作之前,它会测试某个数是 NaN 还是 INFINITY(这是由于针对 NaN 和 INFINITY 的操作可能会增加 FP 异常,而函数规范不允许这一点)。在 POWER7 上,isinf
isnan 函数调用被编译器优化为 FP 指令,并且不会产生额外的函数调用,而在较旧的处理器(POWER6 和更旧的版本)上,它将对各函数分别生成一个调用。优化基本上是相同的实现,但是内联的,以避免函数调用。

最后,比较这两种实现,执行下面的简单测试。在使用和不使用新算法的情况下重新编译 GLIBC,比较每个基准运行的总时间。默认 GLIBC 实现结果位于
清单 25
中:

清单 25

				
$ /usr/bin/time ./hypot_bench_glibc
INF_CASE       : elapsed time: 14:994339 
NAN_CASE       : elapsed time: 14:707085 
TWO60_CASE     : elapsed time: 12:983906 
TWO500_CASE    : elapsed time: 10:589746 
TWOM500_CASE   : elapsed time: 11:215079 
NORMAL_CASE    : elapsed time: 15:325237 
79.80user 0.01system 1:19.81elapsed 99%CPU (0avgtext+0avgdata 151552maxresident)k 
0inputs+0outputs (0major+48minor)pagefaults 0swaps

 

优化版本的结果如
清单 26
中所示:

清单 26

				
$ /usr/bin/time ./hypot_bench_glibc 
INF_CASE       : elapsed time: 4:667043 
NAN_CASE       : elapsed time: 5:100940 
TWO60_CASE     : elapsed time: 6:245313 
TWO500_CASE    : elapsed time: 4:838627 
TWOM500_CASE   : elapsed time: 8:946053 
NORMAL_CASE    : elapsed time: 6:245218 
36.03user 0.00system 0:36.04elapsed 99%CPU (0avgtext+0avgdata 163840maxresident)k 
0inputs+0outputs (0major+50minor)pagefaults 0swaps 

 

这最终将获得超过 100% 的性能提高,使基准时间减少一半。

结束语

含有硬件计数器分析的性能评估是一个功能强大的工具,可以了解工作负载在特定处理器上的行为方式,并给出关于可在哪些地方进行性能优化的提示。最新的 POWER7 处理器有数百个可用的性能计数器,所以我们介绍了一个简单的模型,说明如何将工作负载映射到 CPU 停滞。了解 POWER7 CBM 有点复杂,所以我们还介绍了一些适用于 Linux 的工具,以便简化它。性能评估的策略集中在如何找到热点,如何理解应用程序的内存模式,以及如何使用 POWER7 CBM。最后,我们使用了 GLIBC 内的三角函数上所完成的最新优化来解释用于优化代码的性能分析。

 

抱歉!评论已关闭.