虽然已经有很多工具可以测量硬件性能计数器,但是仍然缺少一个轻量的命令行工具进行简单的端到端测量。Linux MSR模块,提供了从用户空间访问模型专用寄存器的接口,使我们能够不修改Linux内核就能读取Linux读取硬件性能计数器。此外,最近的Intel系统通过PCI接口提供非核心的硬件计数器。
likwid-perfctr
支持以下模式:
likwid-perfctr
作为你的程序的封装器,可以在不修改你的代码的情况下测量。likwid-perfctr
控制要测量什么。有一些预配置的事件集,称为性能组,是一组预选的事件集和根据事件集计算的指标。另外,你也可以指定一个自定义的事件集,在一个事件集中,你可以选择CPU插槽(socket)提供的任意多个物理计数器。请参见架构定义页获得更多详细信息。likwid-perfctl
会在启动时检查事件配置的一组计数器是否可用。
由于likwid-perfctl执行简单的端到端测量,对执行的代码一无所知,因此固定(pin)应用程序至关重要。测量与代码之间的完全是通过固定来建立关系。由于likwid完全在用户空间工作,所以不可能测量单个进程,LIKWID始终测量CPU或插槽(socket)。likwid-perfctl
具有likwid-pin
内置的所有功能,你不需要用其它的工具固定。如果需要你也可以自己控制与CPU内核的亲和力。
likwid-perfctl
的性能组是简单的文本文件,很容易修改和拓展。创建自己的性能组和指标非常简单。相对于以前版本的LIKWID,更新性能组不用再重新编译了。
有关likwid-perfctl
对特定CPU架构的可用计数器和相关计数器选项的信息,点击下面的链接。
不同的访问模式(直接访问或访问守护进程)需要的先决条件不一样。
MSR设备文件必须存在。可以使用ls /dev/cpu/*/msr
进行检查,每个CPU应该列出一个msr设备文件。如果没有这些文件,请尝试通过sudo modprobe msr
加载msr内核模块,然后再次检查MSR设备文件。为了在启动时加载模块,可以在/etc/modules
中添加一行msr
,文件名可能因系统版本而异。
与访问守护进程方式相比,直接访问模式的开销更小,但是要求用户有管理员权限。在config.mk
中设置ACCESSMODE=direct
使用这个功能。
sudo chmod +rw /dev/cpu/*/msr
添加访问MSR设备文件的权限。sudo setcap cap_sys_rawio+ep <PREFIX>/bin/likwid-lua
,其中<perfix>
是安装路径。由于功能系统有点奇怪并且依赖于操作系统,因此这一步可能并不够。并且这个设置只提供core-local计数器,不支持Uncore。为了使普通用户能够访问硬件性能计数器,可以使用守护进程。作者在编写时牢记安全性的要求,它限制了硬件性能相关寄存器的访问,所以用户不能读写系统相关寄存器。当你在config.mk
中选择ACCESSMODE=accessdaemon
,你只需要通过sudo make install
安装LIKWID。这会为守护进程设置适当的权限。不要改变config.mk
中的CHOWN
变量除非你想用不同的权限(允许访问MSR设备文件的组,root用户的不同名字等)。
关于Linux内核5.9及更新版本的更新:使用Linux 5.9时,msr
内核模块得到了一些安全更新,对于LIKWID的主要改变是,现在默认情况下所有的MSR都是不可写的。为了改变这种情况,你需要修改引导选项,添加msr.allow_writes=on
以允许写操作。这只会影响ACCESSMODE=direct
和ACCESSMODE=accessdaemon
。如果你设置了perf_event
后端,那就不用修改任何东西。
也可以查看INSTALL
文件获取更多细节。在对安全敏感的区域上,比如多用户系统或HPC(高性能计算)集群上,对所有MSR寄存器进行不受控制的访问是一个安全问题。有关此问题的解决方案,请查看Build和likwid-accessD.
-h, --help 帮助信息
-v, --version 版本信息
-V, --verbose <level> 日志打印等级, 0 (only errors), 1 (info), 2 (details), 3 (developer)
-c <list> 要测量的处理器ID (必选), 例如 1,2-4,8
-C <list> 固定线程并测量的处理器ID, e.g. 1,2-4,8
有关更多 <list> 的语法, 参见 likwid-pin
-G <list> 要测量的GPU ID
-g, --group <string> CPU的性能组或自定义事件集字符串
-W <string> 英伟达GPU的性能组或自定义事件集字符串
-H 获得性能组的帮助 (与 -g 选项一起用)
-s, --skip <hex> 用于跳过线程的比特位掩码
-M <0|1> 设置如何访问MSR寄存器, 0=直接, 1=访问守护进程
-a 列出可用的性能组
-e 列出可用事件和计数器寄存器
-E <string> 列出可用事件和对应的计数器中匹配<string>的计数器(不区分大小写)
-i, --info 打印CPU信息
-T <time> 以给定的频率切换事件集
模式:
-S <time> 听诊器模式的周期,以s, ms 或 us 为单位, 比如 20ms
-t <time> 时间线模式的频率,以 s, ms 或 us 为单位, 比如 300ms
-m, --marker 在代码中使用Marker API
输出选项:
-o, --output <file> 保存输出到文件. (可选: 根据文件名后缀应用文本过滤器)
-O 输出可解析的 CSV 而不是 fancy tables
用以下命令输出帮助:
$ likwid-perfctr -h
有两个需要的选项: -c
配置应测量CPU核心的哪些计数器,-g
指定要测量的性能组和时间集。CPU核心id列表是一个逗号分割的序列,其中可以包含范围,例如1,2,4-7
。可以在likwid-pin支持的从物理处理器id到不同逻辑变量的所有变量中使用此列表。要指出线程和缓存拓扑可以用likwid-topology。由于 likwid-perfctr
只测量处理器不了解你的线程和进程, 因为必须确保你的代码真的运行在likwid-perfctr
感知的处理器上。likwid-perfctr
包含likwid-pin用于固定线程的所有功能。但是你也可以使用其它工具固定或者通过代码功能。
为了收集关于硬件性能和性能组的信息,请使用 -a
, -g
和-H
选项。
输出处理器支持的所有组:
$ likwid-perfctr -a
要获得所有受支持的计数器寄存器和事件列表,执行:
$ likwid-perfctr -e | less
要获取所有支持的事件和对应的匹配字符串的计数器寄存器列表,执行:
$ likwid-perfctr -E <string>
可以通过 -H
和 -g
选项获得特定事件集的帮助:
$ likwid-perfctr -H -g MEM
这将在性能组文件的 LONG
后面打印文本。要自定义性能组,建议添加描述文本和计算指标的公式.
将 likwid-perfctr 用于串行应用程序执行:
$ likwid-perfctr -C S0:1 -g BRANCH ./a.out
这会将应用程序固定到CPU插槽(socket)0上的第二个核心(索引为1
)。并测量该内核的性能组BRANCH。可以在 likwid-pin 上找到有关CPU字符串表示方法的说明。上述串行应用程序的执行结果输出如下图所示:
--------------------------------------------------------------------------------
CPU name: Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
CPU type: Intel Core Haswell processor
CPU clock: 3.39 GHz
--------------------------------------------------------------------------------
YOUR PROGRAM OUTPUT
--------------------------------------------------------------------------------
Group 1: BRANCH
+------------------------------+---------+---------+
| Event | Counter | Core 1 |
+------------------------------+---------+---------+
| INSTR_RETIRED_ANY | FIXC0 | 201137 |
| CPU_CLK_UNHALTED_CORE | FIXC1 | 375590 |
| CPU_CLK_UNHALTED_REF | FIXC2 | 1595994 |
| BR_INST_RETIRED_ALL_BRANCHES | PMC0 | 44079 |
| BR_MISP_RETIRED_ALL_BRANCHES | PMC1 | 3982 |
+------------------------------+---------+---------+
+----------------------------+--------------+
| Metric | Core 1 |
+----------------------------+--------------+
| Runtime (RDTSC) [s] | 3.522605e-03 |
| Runtime unhalted [s] | 1.107221e-04 |
| Clock [MHz] | 7.982933e+02 |
| CPI | 1.867334e+00 |
| Branch rate | 2.191491e-01 |
| Branch misprediction rate | 1.979745e-02 |
| Branch misprediction ratio | 9.033780e-02 |
| Instructions per branch | 4.563103e+00 |
+----------------------------+--------------+`
输出中包含有原始事件计数器的表格和计算出的指标的表格。如果测量多次,会有另一个包括统计数据(求和,最大值,最小值和平均值)的表格。
通常,事件名称和官方处理器手册中的名称相同(用 “_” 替换 “.”)。相关手册位于Intel Software Development Manual 3B Appendix A和(AMD)BIOS and Kernel Developers Guides (BKDG)。你也可以在供应商提供的优化手册中查看感兴趣的事件集,或者在Intel的性能监控数据库中查看(https://download.01.org/perfmon/)。Intel系统上存在 OFFCORE_RESPONSE
事件但是不符合Intel符号,你必须使用 OFFCORE_RESPONSE_0/1_OPTIONS
事件并使用其事件选项 match0
(低寄存器部分) 和match1
(高寄存器部分)自己指定过滤寄存器的比特位. LIKWID还引入了一些官方文档中找不到的事件,它们是大家都知道的事件,事件带有预配置的事件选项。
对于线程使用,除了-c
参数外没有任何变化。该应用程序在线程支持下进行编译。不需要设置OMP_NUM_THREADS
和CILK_WORKERS
,likwid-perfctr
会根据给定的CPU列表自动设置。如果已经设置了环境变量,likwid-perfctr
不会覆盖它们。
$ likwid-perfctr -C 0-3 -g BRANCH ./a.out
--------------------------------------------------------------------------------
CPU name: Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
CPU type: Intel Core Haswell processor
CPU clock: 3.39 GHz
--------------------------------------------------------------------------------
YOUR PROGRAM OUTPUT
--------------------------------------------------------------------------------
Group 1: BRANCH
+------------------------------+---------+----------+---------+----------+---------+
| Event | Counter | Core 0 | Core 1 | Core 2 | Core 3 |
+------------------------------+---------+----------+---------+----------+---------+
| INSTR_RETIRED_ANY | FIXC0 | 15585960 | 5526616 | 7679943 | 4045942 |
| CPU_CLK_UNHALTED_CORE | FIXC1 | 15025112 | 4660629 | 7745757 | 3406840 |
| CPU_CLK_UNHALTED_REF | FIXC2 | 44696128 | 9473964 | 22825288 | 3762474 |
| BR_INST_RETIRED_ALL_BRANCHES | PMC0 | 1470984 | 752872 | 1163894 | 345736 |
| BR_MISP_RETIRED_ALL_BRANCHES | PMC1 | 9457 | 8238 | 25573 | 1025 |
+------------------------------+---------+----------+---------+----------+---------+
+-----------------------------------+---------+----------+---------+----------+------------+
| Event | Counter | Sum | Min | Max | Avg |
+-----------------------------------+---------+----------+---------+----------+------------+
| INSTR_RETIRED_ANY STAT | FIXC0 | 32838461 | 4045942 | 15585960 | 8209615.25 |
| CPU_CLK_UNHALTED_CORE STAT | FIXC1 | 30838338 | 3406840 | 15025112 | 7709584.5 |
| CPU_CLK_UNHALTED_REF STAT | FIXC2 | 80757854 | 3762474 | 44696128 | 20189463.5 |
| BR_INST_RETIRED_ALL_BRANCHES STAT | PMC0 | 3733486 | 345736 | 1470984 | 933371.5 |
| BR_MISP_RETIRED_ALL_BRANCHES STAT | PMC1 | 44293 | 1025 | 25573 | 11073.25 |
+-----------------------------------+---------+----------+---------+----------+------------+
+----------------------------+--------------+--------------+--------------+--------------+
| Metric | Core 0 | Core 1 | Core 2 | Core 3 |
+----------------------------+--------------+--------------+--------------+--------------+
| Runtime (RDTSC) [s] | 6.292864e-02 | 6.292864e-02 | 6.292864e-02 | 6.292864e-02 |
| Runtime unhalted [s] | 4.429985e-03 | 1.374134e-03 | 2.283749e-03 | 1.004468e-03 |
| Clock [MHz] | 1.140153e+03 | 1.668508e+03 | 1.150968e+03 | 3.071098e+03 |
| CPI | 9.640158e-01 | 8.433061e-01 | 1.008570e+00 | 8.420388e-01 |
| Branch rate | 9.437879e-02 | 1.362266e-01 | 1.515498e-01 | 8.545253e-02 |
| Branch misprediction rate | 6.067640e-04 | 1.490605e-03 | 3.329842e-03 | 2.533403e-04 |
| Branch misprediction ratio | 6.429030e-03 | 1.094210e-02 | 2.197193e-02 | 2.964690e-03 |
| Instructions per branch | 1.059560e+01 | 7.340711e+00 | 6.598490e+00 | 1.170240e+01 |
+----------------------------+--------------+--------------+--------------+--------------+
+---------------------------------+--------------+--------------+-------------+----------------+
| Metric | Sum | Min | Max | Avg |
+---------------------------------+--------------+--------------+-------------+----------------+
| Runtime (RDTSC) [s] STAT | 0.25171456 | 0.06292864 | 0.06292864 | 0.06292864 |
| Runtime unhalted [s] STAT | 0.009092336 | 0.001004468 | 0.004429985 | 0.002273084 |
| Clock [MHz] STAT | 7030.727 | 1140.153 | 3071.098 | 1757.68175 |
| CPI STAT | 3.6579307 | 0.8420388 | 1.00857 | 0.914482675 |
| Branch rate STAT | 0.46760772 | 0.08545253 | 0.1515498 | 0.11690193 |
| Branch misprediction rate STAT | 0.0056805513 | 0.0002533403 | 0.003329842 | 0.001420137825 |
| Branch misprediction ratio STAT | 0.04230775 | 0.00296469 | 0.02197193 | 0.0105769375 |
| Instructions per branch STAT | 36.237201 | 6.59849 | 11.7024 | 9.05930025 |
+---------------------------------+--------------+--------------+-------------+----------------+
请注意,在LIKWID之前的版本中,你必须指定使用的线程实现,现在不需要了。LIKWID使用pinning库重载pthread_create
的调用,这种线程解决方案被许多多线程库使用 (当然包括 PThreads,还包括OpenMP, Cilk+, C++11线程)。
在较新的处理器上存在一个与 Uncore 事件相关的问题。Uncore计数器测量每个插槽。因此likwid-perfctr
具有一个插槽锁,确保每个插槽只有一个线程启动计数器,只有一个线程停止计数器。每个插槽第一个初始化的CPU会在整个执行时间内得到并保持锁。注意在统计信息表中包括尚未测量Uncore事件的处理器,因此仅 MAX 和 SUM 值可用。
likwid-perfctr
允许自定义事件集。你可以选择测量物理计数器支持的所有事件。你可以用事件/计数器对的列表(用逗号分隔)定义事件集。**这高度依赖于体系结构!**在Intel架构上,固定目的的事件(如果尚未存在)会自动添加到事件集中。固定事件是retired指令(INSTR_RETIRED_ANY:FIXC0
)、当前频率的时钟周期 (CPU_CLK_UNHALTED_CORE:FIXC1
)和参考时钟(CPU在非暂停状态时)的时钟周期(CPU_CLK_UNHALTED_REF:FIXC2
) 。
看起来像:
$ likwid-perfctr -C 0-3 -g FP_COMP_OPS_EXE_SSE_FP_PACKED_DOUBLE:PMC0,FP_COMP_OPS_EXE_SSE_FP_SCALAR_DOUBLE:PMC1 ./a.out
--------------------------------------------------------------------------------
CPU name: Intel(R) Xeon(R) CPU E5-2690 v2 @ 3.00GHz
CPU type: Intel Xeon IvyBridge EN/EP/EX processor
CPU clock: 3.00 GHz
--------------------------------------------------------------------------------
YOUR PROGRAM OUTPUT
--------------------------------------------------------------------------------
Group 1:
+--------------------------------------+---------+--------------+--------------+--------------+--------------+
| Event | Counter | Core 0 | Core 1 | Core 2 | Core 3 |
+--------------------------------------+---------+--------------+--------------+--------------+--------------+
| Runtime (RDTSC) [s] | TSC | 2.058991e+01 | 2.058991e+01 | 2.058991e+01 | 2.058991e+01 |
| INSTR_RETIRED_ANY | FIXC0 | 99177052283 | 70451946660 | 42327093707 | 14201949658 |
| CPU_CLK_UNHALTED_CORE | FIXC1 | 52549591060 | 37828491423 | 23914813640 | 9075382636 |
| CPU_CLK_UNHALTED_REF | FIXC2 | 45643583430 | 33342410670 | 21380293590 | 8250340590 |
| FP_COMP_OPS_EXE_SSE_FP_PACKED_DOUBLE | PMC0 | 23240920556 | 16616581676 | 9974859861 | 3342307010 |
| FP_COMP_OPS_EXE_SSE_FP_SCALAR_DOUBLE | PMC1 | 9889323 | 10181092 | 10184277 | 10276947 |
+--------------------------------------+---------+--------------+--------------+--------------+--------------+
+-------------------------------------------+---------+--------------+-------------+-------------+----------------+
| Event | Counter | Sum | Min | Max | Avg |
+-------------------------------------------+---------+--------------+-------------+-------------+----------------+
| Runtime (RDTSC) [s] STAT | TSC | 82.35964 | 20.58991 | 20.58991 | 20.58991 |
| INSTR_RETIRED_ANY STAT | FIXC0 | 226158042308 | 14201949658 | 99177052283 | 56539510577 |
| CPU_CLK_UNHALTED_CORE STAT | FIXC1 | 123368278759 | 9075382636 | 52549591060 | 30842069689.75 |
| CPU_CLK_UNHALTED_REF STAT | FIXC2 | 108616628280 | 8250340590 | 45643583430 | 27154157070 |
| FP_COMP_OPS_EXE_SSE_FP_PACKED_DOUBLE STAT | PMC0 | 53174669103 | 3342307010 | 23240920556 | 13293667275.75 |
| FP_COMP_OPS_EXE_SSE_FP_SCALAR_DOUBLE STAT | PMC1 | 40531639 | 9889323 | 10276947 | 10132909.75 |
+-------------------------------------------+---------+--------------+-------------+-------------+----------------+
自定义事件集是FP_COMP_OPS_EXE_SSE_FP_PACKED_DOUBLE:PMC0,FP_COMP_OPS_EXE_SSE_FP_SCALAR_DOUBLE:PMC1
。它定义了在计数器PMC0
和PMC1
上编程的两个事件。Intel平台为计数器FIXC0
、FIXC1
和FIXC2
自动添加的事件。事件 Runtime (RDTSC) [s]
和它的计数器 TSC
是虚拟事件。无法在自定义事件集中定义此事件或计数器。通常程序的运行时会打印在性能组定义的指标表中。自定义事件集没有要计算的指标。为了给自定义事件提供完整的概览,运行时将会作为虚拟事件输出。
常见任务存在预配置的事件集,这些组提供有用的事件集并据此计算常见的指标。我们尝试为所有架构上提供一些基本组。由于功能不同,有些性能组可能是基于特定处理器的。你可以使用likwid-perfctr -a
打印可用的组。关于性能组选择了特定处理器的哪些事件,可以用 -H -g group
选项查看详细的文档,其中包括计算指标会使用哪些事件。
从 LIKWID 4.0.0 版本开始,可以测量多个性能组或自定义事件集。每个事件集都能用所有可用的性能计数器。事件集以 round-robin 方式切换。默认切换周期是两秒,但是可以使用-T <time>
更换此时间。请注意并非所有架构都提供 L2
和L3
性能组,只有CPU架构提供了需要的事件,我们才能提供性能组。
$ likwid-perfctr -C S0:0-3 -g L2 -g L3 -T 500ms ./a.out
上述命令会测量固定在插槽0上的前4个内核的应用程序。它从测量 L2 性能组开始,在500毫秒后切换到L3,再过500毫秒会测量L2。输出只会显示两个性能组的结果,但是只包括分别被测的时间段,因此 runtime_L2 + runtime_L3 = runtime_a.out
。计算指标只包括性能组运行时,而不是整体时间吗,没有外推法或类似的方法。如果你想知道全部运行时间的L2数据量,你需要自己缩放(假设数据量线性增长)。听诊器模式不支持多个组!
Marker API 能够测量代码中的一段。允许区域重叠或嵌套,也可以多次进行一个区域(例如循环)。每个区域的计数器会累加。在多线程的情况下,可以有串行或并行区域。
Marker API 只读取计数器。计数器的配置仍然通过 likwid-perfctr
进行封装。为了使用 LIKWID Marker API,必须包括头文件 likwid.h
并将代码链接到 LIKWID 库。另外如果需要在链接期间启用 Pthreads,通常要在编译器命令行中设置 -pthread
。为了让你快速开关 Marker API,LIKWID 头文件中包含一组宏,可以在软件编译期间通过定义 LIKWID_PERFMON
激活 Marker API。必须在把 LIKWID 添加到头文件中,即使你的 LIKWID is not 不可用,代码也能编译。
对于 gcc
或 icc
看起来像这样:
$ gcc -O3 -fopenmp -pthread -o test dofp.c -DLIKWID_PERFMON -I<PATH_TO_LIKWID>/include -L<PATH_TO_LIKWID>/lib -llikwid -lm
下面这个例子展示了 Marker API 在串行代码的用法:
// This block enables to compile the code with and without the likwid header in place
#ifdef LIKWID_PERFMON
#include <likwid.h>
#else
#define LIKWID_MARKER_INIT
#define LIKWID_MARKER_THREADINIT
#define LIKWID_MARKER_SWITCH
#define LIKWID_MARKER_REGISTER(regionTag)
#define LIKWID_MARKER_START(regionTag)
#define LIKWID_MARKER_STOP(regionTag)
#define LIKWID_MARKER_CLOSE
#define LIKWID_MARKER_GET(regionTag, nevents, events, time, count)
#endif
LIKWID_MARKER_INIT;
LIKWID_MARKER_THREADINIT;
LIKWID_MARKER_START("Compute");
// Your code to measure
LIKWID_MARKER_STOP("Compute");
LIKWID_MARKER_CLOSE;
对于并行代码,从程序的串行部分调用以下函数序列非常重要:
LIKWID_MARKER_INIT;
[...]
LIKWID_MARKER_CLOSE;
如果将 Marker API 和 likwid-accessD 一起使用,高度建议在每个想测量的代码段执行下面的代码,参数是标识符
LIKWID_MARKER_REGISTER(string);
这将创建基本结构,并建立与守护进程的链接,如果不这样做,当代码只运行很短一段时间时,代码中第一个区域的值将会是 off/lower。
为了方便,还有一个简单的API来固定代码的或进程并获取处理器ID。
likwid_pinProcess(int processorId);
likwid_pinThread(int processorId);
likwid_getProcessorId();
LIKWID 从 4.0.0 版本开始引入了一些新 Marker API 调用:在多个事件集之间切换 (相比于其它API会产生更多开销):
LIKWID_MARKER_SWITCH;
另外,如果你想减少 LIKWID_MARKER_START
的开销,可以实现给代码段注册名字。这会避免串行创建哈希表,引发时间问题。虽然可选,但是高度推荐!
LIKWID_MARKER_REGISTER("Compute")
如果要在应用程序内部处理汇总的测量值,执行:
LIKWID_MARKER_GET("Compute", nevents, events, time, count)
其中nevents
是 int*
,定义了给定数组 events
(类型是 double*
) 的长度,并会在函数返回时被填上数据。time
的类型是 double*
,count
的类型是 int*
.
注意:在区域标签内不允许使用空白字符!
如果想重置一个区域中计数器的值,使用:(在 4.3.3 及以后的版本中可用)
LIKWID_MARKER_RESET("Compute")
该调用必须由每个线程执行,以重置自己的值。
为了使用工具运行可执行文件,你要用 -m
选项激活 likwid-perfctr
的 Marker API:
$ likwid-perfctr -C 0-3 -g BRANCH -m ./a.out
由于CPU列表和事件集是提供给 likwid-perfctr
的而不是编译到程序中的,因此无需修改可执行程序本身就能测量,很灵活。
注意:每个线程分别读取计数器,因此当一个线程的工作量较少时,它会在其它线程之前读取计数器。在低负载线程是读取Uncore计数器的线程时,这个现象很重要。Uncore 计数器是基于特定CPU插槽的。Likwid通常使用一个CPU插槽的亲和性列表中的第一个硬件线程。例如有2个插槽0,1,2,3和4,5,6,7,并且在1,2,3,5,6,7上运行程序,1和5会被用于测量 Uncore 计数器。因此,他们执行用户代码会有延迟,等到他们开始执行用户代码时,可能其它线程开始了某些迭代。
例如:你想用多个线程执行读取内存数据的循环,当负责读取Uncore计数器的线程还在读取已停止的计数器,其它线程已经从内存中加载或向内存中保存了数据。此数据块未被计算在内,因此内存数据量将低于预期。
如果要精确测量,请使用barrier或有条件的等待来同步线程。
我们正在考虑提供MarkerAPI调用,其中包括barriers及一些环境变量,在所有MarkerAPI调用中激活barrier。
可以在 Fortran 90 中使用 LIKWID Marker API 的原生接口。必须在 config.mk
文件中启用,默认不启用。如果启用它,则会设置 Intel Fortran 编译器标志。要改为 gfortran
,编辑 ./make/include_GCC.mk
用相应的标志设置 gfortran
。必须注意 fortran 接口模块 likwid.mod
在你的模块的 include path 中,并且要与 likwid 库连接。
对于 Intel fortran 编译器,看起来像下面这样:
$ ifort -I<PATH_TO_LIKWID>/include -O3 -o fortran chaos.F90 -L<PATH_TO_LIKWID>/lib -llikwid -lpthread -lm -DLIKWID_PERFMON
下面的例子展示了如何在测试目录(chaos.F90)和示例目录(F-markerAPI.F90)中的 Fortran 中使用 Marker API:
call likwid_markerInit()
call likwid_markerThreadInit()
call likwid_markerStartRegion("sub")
! Do stuff
call likwid_markerStopRegion("sub")
call likwid_markerClose()
C Marker API 中的所有函数都可用于 Fortran 90,包括 likwid_markerRegisterRegion
、likwid_markerNextGroup
和likwid_markerGetRegion
。
当检测到代码关闭 Marker API 时,它会将结果写入磁盘。默认是 /tmp
文件夹,通用文件名是 likwid_<PID_OF_PERFCTR>.txt
。文件语法如下:
<nrThreads> <nrRegions> <nrGroups>
<regionID_1>:<regionName_1>
[...]
<regionID_n>:<regionName_n>
<regionID_1> <groupID> <cpuID_1> <callCount> <regionTime> <nrEvents> <event1> <event2> ... <eventM_g>
<regionID_1> <groupID> <cpuID_2> <callCount> <regionTime> <nrEvents> <event1> <event2> ... <eventM_g>
[...]
<regionID_n> <groupID> <cpuID_L-1> <callCount> <regionTime> <nrEvents> <event1> <event2> ... <eventM_g>
<regionID_n> <groupID> <cpuID_L> <callCount> <regionTime> <nrEvents> <event1> <event2> ... <eventM_g>
LIKWID 团队现在不打算为其它语言提供 Marker API。但由于LIKWID是开源的,每个人都可以为他喜欢的语言创建模块。以下是为其它语言提供API的项目列表:
在LIKWID最近的版本中,很容易新定义或修改性能组。所有的组都是根据$HOME/.likwid/groups/ARCH/目录中的文本文件指定的,其中ARCH是处理器微体系结构的缩写。你可以通过运行 likwid-perfctr -i
获得缩写 (自5.11.2015版本开始)。添加新组或更改现有组就是修改文本文件,下面的示例说明了格式:
SHORT Double Precision MFlops/s
EVENTSET
FIXC0 INSTR_RETIRED_ANY
FIXC1 CPU_CLK_UNHALTED_CORE
PMC0 FP_COMP_OPS_EXE_SSE_FP_PACKED
PMC1 FP_COMP_OPS_EXE_SSE_FP_SCALAR
PMC2 FP_COMP_OPS_EXE_SSE_SINGLE_PRECISION
PMC3 FP_COMP_OPS_EXE_SSE_DOUBLE_PRECISION
METRICS
Runtime [s](s]) FIXC1*inverseClock
CPI FIXC1/FIXC0
DP MFlops/s (DP assumed) 1.0E-06*(PMC0*2.0+PMC1)/time
Packed MUOPS/s 1.0E-06*PMC0/time
Scalar MUOPS/s 1.0E-06*PMC1/time
SP MUOPS/s 1.0E-06*PMC2/time
DP MUOPS/s 1.0E-06*PMC3/time
LONG
Double Precision MFlops/s
语句的顺序很重要。第一个标记是该组的 简短
描述。后面是要测量的事件列表,LIKWID会在初始化时跳过不存在的计数器。首先是性能计数器寄存器,然后是用空格分开事件。支持的事件可以从-e
选项打印的列表中获取。 你可以指定事件的更多选项,通过把他们添加到寄存器定义中 :OPT1=VAL1:OPT2=VAL2 。在 METRICS
标签后面,是一个要计算的指标的列表,一行一个指标。每个指标都由一个公式(无空格)和一个简短说明(会出现的表格中)组成。公式要遵循C语法。可以在度量标准中使用的预设变量是 time
(表示运行时间) 和inverseClock
(表示当前处理器的逆时钟)。由于性能组是用 Lua 解释的,不用再重新编译 LIKWID 了。
通过提交 https://github.com/RRZE-HPC/likwid/commit/4849571d833315c96e2e14c8e16ea1a222587730,我们添加了一个额外的文件夹,用于检查性能组文件。由于每个用户都可以创建自己的性能组,因此 LIKWID 现在还会检查文件夹 $HOME/.likwid/groups/ARCH。
通过提交 https://github.com/RRZE-HPC/likwid/commit/75bb5737f039c0bf739ebfe728f78768d5748eeb 和 https://github.com/RRZE-HPC/likwid/commit/88dedc39e02d3b7e171503807836a1d70f1b13ec 我们将内部计算器改为Lua,从这些提交开始,你不需要在指标公式中指定事件选项,为了反映这一点,所有性能组都进行了修改。
likwid-perfctr 允许测量时间解析的配置文件。通过
$ likwid-perfctr -c N:0-7 -g BRANCH -t 2s > out.txt
你可以每2秒测量一次CPU内核0-7上机器的分支行为。这意味着计数器运行会被每两秒执行一次,这以轻量方式实现,输出到 stderr。具有自定义事件集的时间线模式的语法是:
<groupID> <numberOfEvents> <numberOfThreads> <Timestamp> <Event1_Thread1> <Event1_Thread2> ... <EventN_ThreadN>
对于自定义事件集和性能组,时间线模式的输出是不同的。对于自定义事件集, EventX
指的是事件X的原始次数,而对于性能组 EventX
指的是 MetricX
,因此列出了不同线程的指标。通常在使用性能组时,人们对指标和原始计数更感兴趣,因此我们更改了行为。因此,对于性能组:
<groupID> <numberOfEvents> <numberOfThreads> <Timestamp> <Metric1_Thread1> <Metric1_Thread2> ... <MetricN_ThreadN>
可以用命令行设置多个事件集。在每个测量周期后,事件集将以循环方式切换到下一个时间集。请注意,当你希望每2s
读取一次具有多个事件集的数据时,需要每隔 2*<numberOfEventSets>
秒执行一次读取。
注意:尽管LIKWID允许微秒级别的测量,仍有几点需要考虑。测试表明对于低于100毫秒的测量,周期打印的结果不再有效 (高于预期),但结果的性质仍然有效。例如,如果你想解决突发内存传输的问题,则需要小间隔的结果。每次测量的内存带宽可能高于预期,甚至可能高于机器的理论最大值,但是通过内存带宽结果的高低可以清楚地识别突发流量和非突发流量。
likwid-perfctr 允许监听特定时间发生的事情。如果你要查看长期运行的应用程序在性能方面的表现,这将很有用。我们使用它来分析MPI代码,而我们可能无法访问该代码。像likwid-agent一样,听诊器模式也适用于监视。注意不要过分依赖这些测量,因为您不知道代码实际上在做什么,所以可能导致结果不稳定,具体取决于测量的时间段。但是仍然可以让你了解基本性能属性情况。
监视前8个处理器上的分支行为10秒钟:
$ likwid-perfctr -c N:0-7 -g BRANCH -S 10s
可以使用选项 -o
指定输出到文件。如下一节所述,文件名可以包含占位符例如 PID (进程ID) 或 MPI rank。你还必须提供文件扩展名,LIKWID 输出子系统在这里发挥作用。LIKWID 支持通用文本和CSV格式。如果指定 .txt
作为后缀,则原始文本将输出至此文件。如果指定另一个后缀,likwid-perfctr将CSV输出到一个临时文件,并根据后缀名称调用脚本转换成任意格式或进行结果过滤。在LIKWID树中,所有的过滤器都放在 filters
文件夹中。目前只有用于XML和JSON输出的过滤器。你可以通过向该目录添加新脚本来添加其它输出过滤器。我的脚本使用Perl,您也可以用其他脚本语言。如果找不到过滤器脚本,则将临时文件重命名为所需文件。
这使你可以定制likwid-perfctr的输出,使其完全适应您的工具链。还有一个开关(-O
)可以直接生成CSV而无需调用脚本。如果将likwid-perfctr作为监视后端,这会很有用。
注意: likwid-mpirun 提供了一个更简单的接口用于测量MPI和混合应用程序的硬件性能计数器。
为了将likwid-perfctr用于MPI程序,最重要的问题是关注要将进程固定在哪些内核上,并告诉likwid-perfctr哪个内核属于哪些进程。现在的方法是用任务集固定MPI进程,并使用 -c N:0
符号调用 likwid-perfctr,以在此CPU集合中使用逻辑固定。为了区分多个进程的输出,-o
选项允许输出所有结果到文件。
下面的占位符可以用于输出文件名:
%j
- 环境变量 PBS_JOBID
%r
- MPI Rank for Intel MPI (env var PMI_RANK
) and OpenMPI (env var OMPI_COMM_WORLD_RANK
)%h
- 主机名%p
- 进程 ID (PID)输出文件名指定为:
$ mpiexec -np <numberofProcesses> likwid-perfctr -c L:N:0 -g BRANCH -o test_%h_%p.txt ./a.out
有关LIKWID和MPI组合的更详细的说明,参见这里。
在编译 LIKWID 之前,将config.mk
中的USE_PERF_EVENT
设置为 true
。这将禁用 LIKWID 本机的后端并集成 perf_event
。在初始化文件 /proc/sys/kernel/perf_event_paranoid
时,会检查某些函数,一些函数仅限于执行偏执值允许的操作:
-1
- 允许测量整个CPU,而不仅仅是特定的PID。允许读取 Uncore 计数器。LIKWID不支持读取原始追踪点的附加功能,因此不要设置成 0
.0
- 允许测量整个CPU,而不仅仅是特定的PID。允许读取 Uncore 计数器。1
- 允许对PID进行测量,已识别已启动的应用程序。不允许读取 Uncore!2
- 允许对PID进行测量,已识别已启动的应用程序。 (只包括用户空间). 不允许读取 Uncore!对于偏执等级 1
和 2
需要在命令行中设置 --execpid
选项,以确保仅测量启动的应用程序。对于 0
和 -1
,可以忽略 --execpid
选项以测量所选CPU的一切。(正如LIKWID的默认后端那样)
可以在命令行选项中设置 --perfflags
为 perf_event 定义标志。
当前不支持选项,因为需要LIKWID选项名称到perf_event选项名称的转换表。
如果要测量某些其他应用程序,则可以使用命令行选项--perfpid
。
在 LIKWID 5.0 中引入了 Nvidia GPU 后端,允许使用GPU MarkerAPI测量单个内核,以后会允许所有内核。为了正常工作,CUDA 和 CUPTI 必须在 $LD_LIBRARY_PATH 中。可以通过运行 likwid-topology 查看是否输出了你的GPU检测有效性。
注意: 当前,LIKWID使用的CUPTI Event API已被弃用,因此不能在比Nvidia Volta更新的GPU芯片上使用。对于Volta、Turing 和更高版本,应该会有 Nvidia PerfWorks API,但是还没有公开发布
在LIKWID 5.0中存在一个名为 GENERIC_EVENT
的新事件,适用于所有支持的寄存器类型。有时,时间定义不是事件名称而是而是16禁止的配置寄存器设置,整个寄存器0x437805
的一个值或用逗号分隔的字段 config=0x05,umask=0x78
。对于这种情况,可以用 GENERIC_EVENT
: GENERIC_EVENT:<COUNTER>:CONFIG=0x05:UMASK=0x78
。<COUNTER>
告诉LIKWID使用哪个单位和计数器。如果 <COUNTER>
提供了额外的选项比如 THRESHOLD
或 EDGEDETECT
,也可以使用这些选项。