使用 PerfSuite 测量和改进应用程序性能
在某个时候,所有软件应用程序的开发人员,无论是否以 Linux 为目标,都可能会花费至少少量时间专注于其应用程序的性能。原因很简单:从调整软件以提高性能中可以获得许多潜在的好处。例如,在科学和工程领域,性能提升可以区分运行较小规模的模拟与运行更大规模且可能更准确的模型,后者可以提高结果的科学质量。更面向用户的应用程序也将从改进中受益,这些改进可以提高对用户的响应速度并改善整体用户体验。
尽管过去十年左右微处理器的改进使时钟速度远远超过千兆赫兹范围变得司空见惯,但大多数开发人员都知道,处理器频率提高十倍并不能保证应用程序的运行时间缩短十倍。此外,对于那些为他人分发软件的开发人员来说,当您考虑到最终用户可能在 1990 年代中期的 100MHz 奔腾处理器上运行您的应用程序时,对性能和响应能力的关注可以带来巨大的回报。
本文介绍了 PerfSuite 这套开源软件工具,它可以帮助您了解并可能提高您的应用程序在 Linux 下的性能。PerfSuite 由几个相关的工具和库组成,目标是面向性能分析的几种不同活动。
PerfSuite 的开发动机来自于我自己在开发应用程序以及在学术界和企业环境中开发许多大型超级计算机级应用程序的经验。在与几个研究小组合作后,我意识到开发人员通常只利用他们可能获得的一小部分工具。他们通常依赖于传统的基于时间的统计分析技术,例如 gprof。
当然,gprof 风格的分析非常宝贵,应该成为任何开发人员性能工具箱的主要内容。然而,当今的微处理器,例如您可能正在使用的 Linux 上的微处理器,提供了先进的功能,可以提供对直接影响软件性能的特性的另一种见解。特别是,当今常用的几乎所有微处理器都在其设计中集成了基于硬件的性能测量支持。这种支持可以提供软件性能的另一种视角。虽然基于时间的分析告诉您您的软件时间花在了哪里,但硬件性能测量可以帮助您了解处理器在做什么以及处理器被利用的效率。硬件测量还可以查明 CPU 停顿而不是完成有用工作的具体原因。
我第一次遇到“硬件性能计数器”这个术语是在可以访问价值数百万美元的超级计算机的背景下,在这些计算机上,每个 CPU 周期都至关重要,研究团队花费大量时间调整他们的代码,以便从系统中提取最大性能。通常,软件是专门为每种要运行的计算机量身定制的。研究团队有时会仔细研究这些性能计数器生成的数字,以测量其应用程序的确切性能,并找出他们可能获得额外加速的位置。不用说,这一切听起来对我来说都很奇特。但是计数器的目的和功能被证明很简单:它们是添加到 CPU 的额外逻辑,可以准确且以最小的开销跟踪处理器内部的低级操作或事件。
例如,即使您不是计算机架构专家,您可能也已经知道,常用的几乎所有处理器都是基于缓存的机器。缓存提供比主内存快得多的数据和指令访问速度,它基于时间和空间局部性原理。换句话说,缓存设计希望利用许多应用程序的趋势,即在首次使用后不久重用数据块(时间局部性),并访问已使用数据项附近的数据项(空间局部性)。如果您的应用程序遵循这些模式,那么您在基于缓存的处理器上获得高性能的机会就会大大增加。如果不是,您的性能可能会令人失望。如果您有兴趣改进性能不佳的应用程序,那么您的下一个任务是尝试确定处理器为何停顿而不是完成有用的工作。这就是性能计数器可能有所帮助的地方。
需要进行一些研究才能了解特定处理器上可用的性能计数器。每个 CPU 都有不同的可用性能计数器集,通常具有不同的名称。事实上,同一处理器系列中的不同型号在可用的特定性能计数器方面可能会有很大差异。一般来说,计数器测量类似类型的事物。例如,它们可以记录缓存未命中的绝对数量、发出的指令数量、执行的浮点指令数量以及向量指令(例如 SSE 或 MMX)的数量。有关处理器上可用计数器的最佳参考是供应商的处理器技术参考,通常可在 Web 上找到。
另一个复杂之处是需要内核级支持才能访问性能计数器。虽然 Itanium (IA-64) 内核通过官方内核中的 perfmon 驱动程序(由 HP Research 的 Stephane Eranian 编写)提供此支持,但标准的 x86 Linux 树目前没有。
幸运的是,正在努力解决这些问题。第一个是为 x86 内核开发一个名为 perfctr 的性能监控驱动程序。这是一个非常稳定的内核补丁,由瑞典乌普萨拉大学的 Mikael Pettersson 开发。perfctr 内核补丁正变得越来越被社区广泛采用,并不断得到改进和维护。第二个是田纳西大学诺克斯维尔分校创新计算实验室的一项努力,称为 PAPI(性能应用程序编程接口)。PAPI 定义了一组标准的跨平台性能监控事件和一个标准 API,允许以可移植的方式使用硬件计数器进行测量。PAPI 项目为几个当前的处理器和操作系统提供了库的实现,包括 Intel/AMD x86 处理器、Itanium 系统以及最近的 AMD x86-64 CPU。在 Linux 上,PAPI 根据需要使用 perfmon 和 perfctr 驱动程序。请参阅在线资源以获取参考,您可以在其中了解更多关于 perfctr、perfmon 和 PAPI 的信息。
本文余下部分讨论的 PerfSuite 构建于 PAPI、perfmon 和 perfctr 之上,为开发人员提供了更高级别的用户界面以及附加功能。PerfSuite 的主要重点是易用性。根据我在与对性能分析感兴趣的开发人员合作的经验,很明显,理想的解决方案将只需要用户进行很少或不需要额外的工作,他们只想知道应用程序在计算机上的性能如何。他们想知道这一点,而无需学习如何在低级别配置或访问性能数据的许多细节。
假设您的应用程序不适合缓存友好型 - 会发生什么?在最坏的情况下,它可能不会将数据行加载到缓存中并重复操作该行中包含的数据,而是可能只使用一条数据然后就完成了。您需要的下一条数据可能需要加载另一条缓存行,依此类推。这些缓存加载中的每一个都相对昂贵,并可能导致性能下降,因为处理器主要等待它需要的数据变为可用。每次需要下一条数据时,处理器都会尝试从已驻留在缓存中的数据中加载它。如果找不到,则会发生缓存未命中,并发出相应的硬件事件信号。缓存未命中与命中的比率越高,软件的整体性能就越有可能降低。
清单 1 显示了一个基本但具体的示例,说明这可能如何发生。该清单显示了一个循环,该循环使用另一个矩阵和一个向量的对应元素的总和来初始化矩阵的每个元素。由于 C 语言以行优先顺序存储数据,因此编写的循环不会访问两个矩阵中的相邻数据元素。幸运的是,这个问题有一个简单的解决方案:交换嵌套循环,以便按行处理矩阵。这种数组访问模式也称为步长为 1 的访问。许多优化编译器会自动执行这种类型的循环交换优化,具体取决于您选择的优化级别。
清单 1. 来自具有缓存不友好行为的程序的循环
for (j = 0; j < COLS; j++) for (i = 0; i < ROWS; i++) a[i][j] = b[i][j] + c[i]
包含这两个版本循环的测试用例使用最新版本的 Intel ICC 编译器编译,在奔腾 III 计算机上运行并计时。这个简单的更改使循环速度提高了十倍。不出所料,优化版本的循环的整体二级缓存未命中计数显着减少(212,665,026 对比 25,287,572 - 有关更多信息,请参阅下一节)。
通常,将原始硬件性能计数合并为派生指标很有用,该指标可以提供性能的标准化视图。例如,最广泛使用的性能测量指标之一描述了完成一条指令所需的平均周期数 (CPI)。通过计算周期总数和已退役指令数(两者都作为硬件事件提供),我们可以轻松获得此指标。同样,我们可能对了解平均而言,一条数据一旦驻留在缓存中被重用了多少次感兴趣。通过计算相关的缓存事件并将它们组合成一个指标,我们也可以获得此信息的近似值。
PerfSuite 的硬件性能计数器工具和库提供了对原始测量数据以及大量派生指标的轻松访问,您可以使用这些指标来了解并希望提高应用程序的性能。在其最基本的使用中,PerfSuite 只需要对您执行的命令稍作修改即可运行您的程序。如果您的可执行文件在文件 myprog 中,那么您不是直接运行 myprog,而是输入psrun myprog。如果一切顺利,psrun 的输出是一个 XML 文档,其中包含一组标准的硬件事件以及有关 CPU 的其他信息。您可以使用命令将此 XML 文档转换为全面的性能报告psprocess,并为其提供 XML 文件的名称。
当前版本的 PerfSuite 包括以下四个工具,用于访问和处理性能数据
psrun:一个用于硬件性能事件计数和单线程、基于 POSIX 线程和 MPI 应用程序的分析的实用程序。
psprocess:一个实用程序,可协助处理与性能测量的预处理和后处理相关的许多常见任务。
psinv:一个实用程序,提供对有关机器特性的信息的访问(例如,处理器类型、缓存信息和可用的性能计数器)。
psconfig:一个图形工具,用于轻松创建和管理 PerfSuite 配置文件。
本节演示了两个命令 psrun 和 psprocess。访问 PerfSuite 网站以获取有关 psinv 和 psconfig 使用的更多信息和示例。
学习使用基本 PerfSuite 工具的最简单方法是在您自己的程序上试用它们。以下是您可能输入的命令序列,用于运行之前讨论的简单缓存示例,并启用性能测量。还显示了每次运行 psrun 后目录的当前内容,以显示已创建 XML 文档
1% ls badcache goodcache 2% psrun badcache 3% ls badcache goodcache psrun.22865.xml 4% psrun goodcache 5% ls badcache goodcache psrun.22865.xml psrun.22932.xml 6% psprocess psrun.22865.xml 7% psprocess psrun.22932.xml
清单 2 和 3 显示了未经优化和优化版本的测试程序的 psprocess 命令的输出;这些清单已稍作编辑以适应可用空间。正如您所见,在测量过程中收集了大量信息,并且报告不仅包括使用 PAPI 测量的原始事件计数,还包括一系列可以从计数中派生的指标。
清单 2. 来自循环的缓存不友好版本的 psprocess 输出
PerfSuite Hardware Performance Summary Report Version : 1.0 Created : Thu Feb 19 22:43:01 2004 Generator : psprocess 0.2 XML Source : psrun.22865.xml Processor and System Information ==================================================== Node CPUs : 2 Vendor : Intel Family : Pentium Pro (P6) CPU Revision : 6 Clock (MHz) : 997.173 Memory (MB) : 1510.82 Pagesize (KB) : 4 Cache Information ==================================================== Cache levels : 2 -------------------------------- Level 1 Type : instruction Size (KB) : 16 Linesize (B) : 32 Assoc : 4 Type : data Size (KB) : 16 Linesize (B) : 32 Assoc : 4 -------------------------------- Level 2 Type : unified Size (KB) : 256 Linesize (B) : 32 Assoc : 8 Index Description Counter Value =================================================== 1 Conditional branch instructions........ 52663367 2 Branch instructions.................... 52650952 3 Conditional branch ins mispredicted...... 112009 4 Conditional branch instructions taken.. 52610596 5 Branch target address cache misses........ 31020 6 Requests for excl acc to clean cache line.. 1165 7 Requests for cache line invalidation.......... 0 8 Requests for cache line intervention...... 32801 9 Requests for excl acc to shared cache ln.. 26537 10 Floating point multiply instructions.......... 0 11 Floating point divide instructions............ 0 12 Floating point instructions........... 208155552 13 Hardware interrupts....................... 22134 14 Total cycles........................ 21407855039 15 Instructions issued.................. 2010041200 16 Instructions completed................ 624104056 17 Vector/SIMD instructions...................... 0 18 Level 1 data cache accesses........... 678945043 19 Level 1 data cache misses............. 244760094 20 Level 1 instruction cache accesses.. 21332388384 21 Level 1 instruction cache misses.......... 22546 22 Level 1 instruction cache reads..... 21309322857 23 Level 1 load misses................... 244318153 24 Level 1 store misses....................... 9852 25 Level 1 cache misses.................. 243826788 26 Level 2 data cache reads.............. 243745402 27 Level 2 data cache writes................. 10317 28 Level 2 instruction cache accesses........ 24335 29 Level 2 instruction cache reads........... 21362 30 Level 2 cache misses.................. 212665026 31 Cycles stalled on any resource...... 21057880641 32 Instruction TLB misses....................... 64 Statistics =================================================== Counting domain............................... user Multiplexed.................................... yes Graduated floating point ins. per cycle...... 0.010 Vector ins. per cycle........................ 0.000 Floating point ins per graduated ins ........ 0.334 Vector ins per graduated ins ................ 0.000 Floating point ins per L1 data cache access.. 0.307 Graduated ins per cycle...................... 0.029 Issued ins per cycle......................... 0.094 Graduated ins per issued ins................. 0.310 Issued ins per L1 ins cache miss......... 89152.896 Graduated ins per L1 ins cache miss...... 27681.365 Level 1 ins cache miss ratio................. 0.000 Level 1 data cache access per graduated ins.. 1.088 % floating point ins of all graduated ins... 33.353 % cycles stalled on any resource............ 98.365 Level 1 ins cache misses per issued ins...... 0.000 Level 1 cache read miss ratio (instruction).. 0.000 Level 1 cache miss ratio (data).............. 0.361 Level 1 cache miss ratio (instruction)....... 0.000 Bandwidth used to level 1 cache (MB/s)..... 363.437 Bandwidth used to level 2 cache (MB/s)..... 316.988 MFLIPS (cycles).............................. 9.696 MFLIPS (wall clock).......................... 9.530 MVOPS (cycles)............................... 0.000 MVOPS (wall clock)........................... 0.000 MIPS (cycles)............................... 29.071 MIPS (wall clock)........................... 28.572 CPU time (seconds).......................... 21.469 Wall clock time (seconds)................... 21.843 % CPU utilization........................... 98.285
清单 3. 来自循环的优化版本的 psprocess 输出的一部分。处理器和系统信息以及缓存信息部分相同。
Index Description Counter Value =================================================== 1 Conditional branch instructions........ 49627213 2 Branch instructions.................... 49971420 3 Conditional branch ins mispredicted....... 97630 4 Conditional branch ins taken........... 49089592 5 Branch target address cache misses......... 3816 6 Requests for excl access to clean cache ln. 820 7 Requests for cache line invalidation.......... 0 8 Requests for cache line intervention....... 2796 9 Requests for excl access to shared cache ln. 494 10 Floating point multiply instructions.......... 0 11 Floating point divide instructions............ 0 12 Floating point instructions........... 189564951 13 Hardware interrupts........................ 2577 14 Total cycles......................... 2471179766 15 Instructions issued................... 513936102 16 Instructions completed................ 509580537 17 Vector/SIMD instructions...................... 0 18 Level 1 data cache accesses........... 372965600 19 Level 1 data cache misses.............. 23010188 20 Level 1 instruction cache accesses... 2769671237 21 Level 1 instruction cache misses........... 2369 22 Level 1 instruction cache reads...... 2746595553 23 Level 1 load misses.................... 25980065 24 Level 1 store misses........................ 995 25 Level 1 cache misses................... 25772544 26 Level 2 data cache reads.............. .25617201 27 Level 2 data cache writes................... 935 28 Level 2 instruction cache accesses......... 2405 29 Level 2 instruction cache reads............ 2652 30 Level 2 cache misses................... 25287572 31 Cycles stalled on any resource....... 2199590592 32 Instruction TLB misses........................ 0 Statistics ================================================== Counting domain.............................. user Multiplexed................................... yes Graduated floating point ins per cycle...... 0.077 Vector ins per cycle.........................0.000 Floating point ins per graduated ins........ 0.372 Vector ins per graduated ins................ 0.000 Floating point ins per L1 data cache access. 0.508 Graduated ins per cycle......................0.206 Issued ins per cycle.........................0.208 Graduated ins per issued ins................ 0.992 Issued ins per L1 ins cache miss....... 216942.213 Graduated ins per L1 ins cache miss.... 215103.646 Level 1 ins cache miss ratio................ 0.000 Level 1 data cache access per graduated ins. 0.732 % floating point ins of all graduated ins.. 37.200 % cycles stalled on any resource........... 89.010 Level 1 ins cache misses per issued ins..... 0.000 Level 1 cache read miss ratio (instruction). 0.000 Level 1 cache miss ratio (data)............. 0.062 Level 1 cache miss ratio (instruction)...... 0.000 Bandwidth used to level 1 cache (MB/s).... 332.792 Bandwidth used to level 2 cache (MB/s).... 326.530 MFLIPS (cycles)............................ 76.493 MFLIPS (wall clock)........................ 66.787 MVOPS (cycles).............................. 0.000 MVOPS (wall clock).......................... 0.000 MIPS (cycles)............................. 205.626 MIPS (wall clock)......................... 179.533 CPU time (seconds).......................... 2.478 Wall clock time (seconds)................... 2.838 % CPU utilization.......................... 87.310
psrun 通过查阅您可以提供的配置文件来确定要测量的性能事件,该文件是一个描述要进行的测量的 XML 文档。如果您不提供配置文件,则使用默认配置文件(清单 2 和 3 中显示的输出使用了默认配置文件)。作为 XML 文档,配置文件可以直接修改和读取。例如,如果您想获得计算先前讨论的 CPI 指标所需的原始事件,则需要请求 psrun 测量已完成指令的总数和周期总数。这些事件在 PAPI 中预定义,分别称为 PAPI_TOT_INS 和 PAPI_TOT_CYC。清单 4 显示了一个 PerfSuite XML 配置文件,可用于测量这些事件。要将此配置文件与 psrun 一起使用,您只需提供选项 -c,以及您的自定义配置文件的名称,然后像往常一样运行即可。
清单 4. 一个示例 PerfSuite XML 配置文件文档
<?xml version="1.0" encoding="UTF-8" ?> <ps_hwpc_eventlist class="PAPI"> <!-- ================================================== Configuration file to measure graduated instructions and total cycles. ================================================== --> <ps_hwpc_event type="preset" name="PAPI_TOT_INS" /> <ps_hwpc_event type="preset" name="PAPI_TOT_CYC" /> </ps_hwpc_eventlist>
到目前为止描述的测量是在聚合计数模式下进行的,其中测量和报告一个或多个性能事件的总计数,该计数是在应用程序的总运行时间内测量的。PerfSuite 提供了另一种查看应用程序性能的方式。假设您有兴趣找出应用程序中所有二级缓存未命中发生的位置,以便您可以将优化工作重点放在那里。换句话说,您希望有一个类似于 gprof 的基于时间的分析的分析,但改为基于二级缓存未命中。这可以使用 psrun 轻松完成,方法是指定为分析而不是聚合计数量身定制的配置文件。PerfSuite 发行版包含许多类似的替代配置文件,您可以根据需要进行定制。以下是如何请求分析实验而不是事件的默认总计数的示例
8% psrun -c \ /usr/local/share/perfsuite/xml/pshwpc/profile.xml \ solver 9% psprocess -e solver psrun.24135.xml
在分析模式下,psprocess 工具还需要您的可执行文件 (solver) 的名称才能完成其工作。这是必需的,以便提取可执行文件中的符号信息,以便可以将程序地址映射到源代码行。
清单 5 显示了以这种方式获得的 psrun 分析运行的示例。不仅分析了应用程序 (solver),而且还列出了与消耗 CPU 时间的应用程序一起使用的共享库。整体性能计数和分析的结合可以成为一个强大的工具,用于了解您的软件中可能存在的瓶颈,并可以帮助您快速隔离最需要关注的应用程序区域。
清单 5. 由 psprocess 基于二级缓存未命中生成的源代码分析(输出已被截断以适应可用空间)。
PerfSuite Hardware Performance Summary Report Profile Information =================================================== Class : PAPI Event : PAPI_L2_TCM (Level 2 cache misses) Period : 10000 Samples : 16132 Domain : user Run Time : 319.72 (seconds) Min Self % : (all) Module Summary --------------------------------------------------- Samples Self % Total % Module 16131 99.99% 99.99% /home/nobody/solver/sol 1 0.01% 100.00% /lib/libc-2.2.4.so File Summary --------------------------------------------------- Samples Self % Total % File 5093 31.57% 31.57% matxvec2d_blk3.f 5015 31.09% 62.66% cg3_blk.f 4162 25.80% 88.46% pc_jac2d_blk3.f 1407 8.72% 97.18% dot_prod2d_blk3.f 429 2.66% 99.84% add_exchange2d_blk3.f 20 0.12% 99.96% glibc-2.2.4/csu/init.c 4 0.02% 99.99% main3.f 1 0.01% 99.99% linuxthreads/weaks.c 1 0.01% 100.00% cs_jac2d_blk3.f Function Summary --------------------------------------------------- Samples Self % Total % Function 5093 31.57% 31.57% matxvec2d_blk3 5015 31.09% 62.66% cg3_blk 4162 25.80% 88.46% pc_jac2d_blk3 1407 8.72% 97.18% dot_prod2d_blk3 429 2.66% 99.84% add_exchange2d_blk3 20 0.12% 99.96% ? 4 0.02% 99.99% main3 1 0.01% 99.99% __pthread_return_0 1 0.01% 100.00% cs_jac2d_blk3 File:Line Summary --------------------------------------------------- Samples Self % Total % File:Line 5089 31.55% 31.55% matxvec2d_blk3.f:19 4125 25.57% 57.12% pc_jac2d_blk3.f:20 2763 17.13% 74.24% cg3_blk.f:206 1346 8.34% 82.59% cg3_blk.f:346 576 3.57% 86.16% dot_prod2d_blk3.f:24 524 3.25% 89.41% cg3_blk.f:278 489 3.03% 92.44% dot_prod2d_blk3.f:23 332 2.06% 94.50% dot_prod2d_blk3.f:25 197 1.22% 95.72% cg3_blk.f:279 176 1.09% 96.81% add_exchange2d_blk3.f:29 99 0.61% 97.42% add_exchange2d_blk3.f:50 71 0.44% 97.86% add_exchange2d_blk3.f:30 71 0.44% 98.30% add_exchange2d_blk3.f:51 55 0.34% 98.64% cg3_blk.f:55 38 0.24% 98.88% cg3_blk.f:207 34 0.21% 99.09% cg3_blk.f:218 31 0.19% 99.28% pc_jac2d_blk3.f:27 24 0.15% 99.43% cg3_blk.f:139 20 0.12% 99.55% init.c:0 8 0.05% 99.60% dot_prod2d_blk3.f:22 5 0.03% 99.63% add_exchange2d_blk3.f:44 4 0.02% 99.66% matxvec2d_blk3.f:17 4 0.02% 99.68% cg3_blk.f:140 3 0.02% 99.70% cg3_blk.f:347 3 0.02% 99.72% cg3_blk.f:268 3 0.02% 99.74% cg3_blk.f:280 3 0.02% 99.76% pc_jac2d_blk3.f:18 3 0.02% 99.78% cg3_blk:/home/nobody/solver/cg3_blk.f:174
本文仅触及了在使用硬件性能计数器来测量和提高应用程序性能时可用的技术的表面。希望您现在对硬件性能计数器是什么以及它们如何帮助您深入了解性能瓶颈有所了解。如果您想开始使用 PerfSuite 或本文中提到的其他工具和支持软件,请访问在线资源。
应用程序可以通过多种不同的方式进行调整以获得更高的性能。事实上,最有效的方法不是循环级改进或调整,而是对应用程序中使用的算法进行根本性更改,使其在计算上更有效。理想情况下,您的软件将使用高效的算法,并进一步调整以有效利用您的 CPU。PerfSuite 和其他类似工具可以在很大程度上使此过程对您来说更容易。
我要感谢弗吉尼亚理工大学机械工程系的 Danesh Tafti 教授提供用于清单 5 中 psrun 分析示例的程序。这是一个从计算流体动力学应用程序 GenIDLEST 中提取的计算内核,Tafti 及其研究团队使用、维护和开发该应用程序。我还要感谢田纳西大学诺克斯维尔分校创新计算实验室的所有 PAPI 团队成员,感谢他们在 PerfSuite 开发期间给予的支持和鼓励。
本文的资源: /article/8264。
Rick Kufrin 目前是伊利诺伊大学国家超级计算应用中心的技术人员高级成员。他是本文中描述的 PerfSuite 软件项目的发起人和技术负责人,并可就 PerfSuite 和其他软件改进技术的使用提供咨询。