收集器可收集三种不同类型的数据:分析数据、跟踪数据和全局数据。
可通过以固定的间隔记录分析事件来收集分析数据。该间隔可以是使用系统时钟获取的时间间隔,也可以是特定类型硬件事件的数目。间隔时间结束时,会向系统传送一个信号,并在下一个间隔记录数据。
可通过在各种系统函数上插入包装函数来收集跟踪数据,以便拦截对系统函数的调用,并记录有关调用的数据。
可通过调用各种系统例程以获取信息来收集全局数据。全局数据包称为样本。
分析数据和跟踪数据都包含有关特定事件的信息,并且这两种类型的数据都会转换为性能度量。全局数据不会转换为度量,而是用于提供标记器,这些标记器可用于将程序执行划分为很多时间段。通过全局数据,可以了解该时间段内程序执行的总体情况。
每个分析事件或跟踪事件收集的数据包都包含以下信息:
标识数据的数据包头
高精度的时间戳
线程 ID
轻量级进程 (lightweight process, LWP) ID
处理器 (CPU) ID,在操作系统中可用时
调用栈的副本。对于 Java 程序,将记录两个调用栈:机器调用栈和 Java 调用栈。
对于 OpenMP 程序,还会收集当前并行区域的标识符和 OpenMP 状态
有关线程和轻量级进程的更多信息,请参见第 7 章,了解性能分析器及其数据。
除了通用数据外,每个事件特定的数据包还包含特定于数据类型的信息。收集器可以记录的五种数据类型为:
时钟分析数据
硬件计数器溢出分析数据
同步等待跟踪数据
堆跟踪(内存分配)数据
MPI 跟踪数据
以下几个小节将介绍这五种数据类型(度量即是根据这五种数据类型得出的),以及如何使用这些数据类型。第六种数据类型(全局抽样数据),无法转换为度量,因为它不包含调用栈信息。
进行基于时钟的分析时,收集的数据取决于操作系统所提供的度量。
在 Solaris OS 下基于时钟的分析中,将按固定的时间间隔存储每个 LWP 的状态。这种时间间隔称为分析间隔。这些信息存储在一个整数数组中:数组的一个元素用于内核维护的十个微记帐状态中的每一个状态。收集的数据通过性能分析器转换为每个状态所用的时间和分析间隔的精度。缺省分析间隔约为 10 毫秒 (10 ms)。收集器提供的高精度分析间隔大约为 1 ms,低精度分析间隔大约为 100 ms,如果 OS 允许,则可使用任意的间隔。运行 collect 命令(不带任何参数)可列显运行该命令的系统所允许的范围和精度。
表 2–1 Solaris 计时度量
度量 |
定义 |
---|---|
用户 CPU 时间 |
在 CPU 中按用户模式运行所用的 LWP 时间。 |
挂钟时间 |
LWP 1 中所用的 LWP 时间。该时间通常称为“挂钟时间”。 |
全部 LWP 时间 |
全部 LWP 时间的总和。 |
系统 CPU 时间 |
在 CPU 中或陷阱状态下按内核模式运行所用的 LWP 时间。 |
等待 CPU 时间 |
等待 CPU 所用的 LWP 时间。 |
用户锁定时间 |
等待锁定所用的 LWP 时间。 |
文本缺页时间 |
等待文本页所用的 LWP 时间。 |
数据缺页时间 |
等待数据页所用的 LWP 时间。 |
其他等待时间 |
等待内核页所用的 LWP 时间,或休眠/ 停止所用的时间。 |
对于多线程实验,将计算所有 LWP 的时间(挂钟时间除外)的总和。所定义的挂钟时间对于多程序多数据 (multiple-program multiple-data, MPMD) 程序没有意义。
计时度量按多种类别说明程序消耗时间的位置,并且可用于改善程序的性能。
高用户 CPU 时间说明程序处理大部分工作的位置,此外还可用于查找重新设计算法后可能受益最多的程序部分。
高系统 CPU 时间说明程序在对系统例程的调用中消耗了大量时间。
高等待 CPU 时间说明准备运行的线程数比可用的 CPU 多,或其他进程正在使用 CPU。
高用户锁定时间说明线程无法获得其请求的锁定。
高文本缺页时间意味着链接程序生成的代码会在内存中进行组织,所以调用或分 支会导致新的页面被装入。创建和使用映射文件(请参见性能分析器联机帮助中的“生成和使用映射文件”)可以修复这种问题。
高数据缺页时间表明对数据的访问会导致新的页面被装入。重新组织程序的数据结构或算法可以修复此问题。
在 Linux OS 下,唯一可用的度量是用户 CPU 时间。虽然报告的总 CPU 占用时间是准确的,但分析器不可能像在 Solaris OS 中那样准确地确定实际系统 CPU 时间的时间比例。虽然分析器显示的信息好像是轻量级进程 (lightweight process, LWP) 数据,但实际上 Linux OS 中没有 LWP 的数据;所显示的 LWP ID 实际上是线程 ID。
硬件计数器可跟踪诸如高速缓存未命中次数、高速缓存停止周期、浮点运算、分支误预测、CPU 周期以及执行指令之类的事件。在硬件计数器溢出分析中,当运行 LWP 的 CPU 的指定硬件计数器溢出时,收集器会记录分析数据包。计数器将重置并继续进行计数。分析数据包中包括溢出值和计数器类型。
各种 CPU 系列支持同时存在二到十八个硬件计数器寄存器。收集器可收集一个或多个寄存器上的数据。对于每个寄存器,收集器都允许您选择计数器的类型来监视溢出,并设置计数器的溢出值。有些硬件计数器可以使用任意寄存器,而有些计数器仅可以使用特定的寄存器。因此,在一个实验中并非可以选择所有的硬件计数器组合。
硬件计数器溢出分析数据由性能分析器转换为计数度量。对于以循环方式计数的计数器,所报告的度量会转换为次数;而对于不以循环方式计数的计数器,所报告的度量为事件计数。在具有多个 CPU 的机器上,用于转换度量的时钟频率为各个 CPU 时钟频率的调和平均数。因为每种类型的处理器都有其自己的一组硬件计数器,并且硬件计数器的数目庞大,因此,此处未列出硬件计数器的度量。下一小节讲述如何找出可用的硬件计数器。
硬件计数器的一个用途是可诊断进出 CPU 的信息流问题。例如,高速缓存未命中次数计数较高表明,重新组织程序的结构来改进数据或文本的位置或提高高速缓存的重用率可以改善程序性能。
某些硬件计数器可提供类似或相关的信息。例如,分支误预测和指令高速缓存未命中次数通常是相关的,因为分支误预测会导致将错误的指令装入到指令高速缓存,而这些指令必须替换为正确的指令。这种替换会导致指令高速缓存未命中,或指令转换后备缓冲器 (instruction translation lookaside buffer, ITLB) 未命中,或甚至缺页。
通常会在导致事件和相应事件计数器溢出的指令之后,向硬件计数器溢出传送一条或多条指令:这称为“失控 (skid)”,它会使计数器溢出分析数据难以解释。如果缺少对精确识别因果指令的硬件支持,则可以对候选的因果指令尝试合适的回溯搜索。
收集期间支持和指定这种回溯时,硬件计数器分析数据包还包括适用于硬件计数器事件的候选内存引用指令的 PC(program counter,程序计数器)和 EA(effective address,有效地址)。(在分析期间需要进行后续处理来验证候选事件 PC 和 EA)。有关内存引用事件的此附加信息有助于进行各种面向数据的分析。
也可以为时钟分析指定候选事件 PC 和 EA 的回溯和记录。
由于硬件计数器是特定于处理器的,因此可以选用的计数器取决于所使用的处理器。性能工具为许多可能常用的计数器提供了别名。通过在特定系统上的终端窗口键入不带参数的 collect,您可以从收集器获得该系统上可用的硬件计数器列表。如果处理器和系统支持硬件计数器分析,则 collect 命令会列显两个包含有关硬件计数器信息的列表。第一个列表包含“周知”(有别名的)硬件计数器;第二个列表包含原始硬件计数器。
以下示例显示了计数器列表中的条目。被认为是周知的计数器将首先显示在列表中,然后是原始硬件计数器列表。该示例中的每一行输出都按打印格式显示。
Well known HW counters available for profiling: cycles[/{0|1}],9999991 (’CPU Cycles’, alias for Cycle_cnt; CPU-cycles) insts[/{0|1}],9999991 (’Instructions Executed’, alias for Instr_cnt; events) dcrm[/1],100003 (’D$ Read Misses’, alias for DC_rd_miss; load events) ... Raw HW counters available for profiling: Cycle_cnt[/{0|1}],1000003 (CPU-cycles) Instr_cnt[/{0|1}],1000003 (events) DC_rd[/0],1000003 (load events) |
在周知硬件计数器列表中,第一个字段(例如 cycles)是可以在 collect 命令的 -h counter... 参数中使用的别名。该别名还是在 er_print 命令中使用的标识符。
第二个字段列出计数器的可用寄存器,例如 [/{0|1}]。对于周知计数器,选择的缺省值可提供合理的抽样率。由于实际抽样率变化相当大,因此可能需要您指定缺省值以外的值。
第三个字段(例如 9999991)是计数器的缺省溢出值。
第四个字段(在圆括号中)包含类型信息。它提供简短描述(例如 CPU Cycles)、原始硬件计数器名称(例如 Cycle_cnt)以及计数单位类型(例如 CPU-cycles,其中最多可以包括两个单词)。
如果类型信息的第一个单词是:
load、store 或 load-store,则表明计数器与内存相关。您可以在 collect -h 命令中的计数器名称前放置一个 + 号(例如 +dcrm),以请求搜索引发事件的准确指令和虚拟地址。+ 号还可以启用数据空间分析;有关详细信息,请参见“数据对象”标签、“数据布局”标签和“内存对象”标签。
not-program-related,计数器会捕获由其他某个程序启动的事件,例如 CPU 到 CPU 的高速缓存嗅探。使用计数器进行分析将生成警告,并且分析不记录调用栈。
如果类型信息的第二个单词或仅有的单词是:
CPU-cycles,则计数器可用于提供基于时间的度量。针对此类计数器报告的度量在缺省情况下会转换为独占时间和包含时间,但是也可以显示为事件计数。
events,则度量是包含和独占事件计数,且无法转换为时间。
在示例中的周知硬件计数器列表中,类型信息包含一个单词的,如第一个计数器的 CPU-cycles 和第二个计数器的 events。类型信息包括两个单词的,如第三个计数器的 load events。
原始硬件计数器列表中包含的信息是周知硬件计数器列表中信息的子集。每行包括由 cpu-track(1) 使用的内部计数器名称、可以在其上使用计数器的寄存器编号、缺省溢出值和计数器单位(可以是 CPU-cycles 或 Events)。
如果计数器度量与运行的程序无关的事件,则类型信息的第一个单词是 not-program-related。对于这样的计数器,分析不会记录调用栈,而是显示人工函数 collector_not_program_related 中所用的时间。线程和 LWP ID 会被记录,但没有任何意义。
原始计数器的缺省溢出值是 1000003。由于该值对于大多数原始计数器来说是不理想的,因此应该在指定原始计数器时指定超时值。
在多线程程序中,不同线程执行的任务同步会导致应用程序的执行延迟,例如,一个线程要访问已被其他线程锁定的数据时就不得不等待。这些事件称为同步延迟事件,并通过跟踪对 Solaris 或 pthread 线程函数的调用来收集这些事件。收集和记录这些事件的过程称为同步等待跟踪。等待锁定花费的时间称为等待时间。目前,只能在运行 Solaris OS 的系统中进行同步等待跟踪。
只有等待时间超过阈值(单位为微秒)时,才会记录事件。阈值为 0 表示跟踪所有的同步延迟事件,而不管等待时间为何。缺省阈值通过运行校准测试决定,在该测试中对线程库的调用不会出现任何同步延迟。阈值是这些调用的平均时间与某个因子(当前为 6)相乘。该过程可防止对此类事件进行记录:即等待时间仅在于调用本身,而与实际的延迟无关。因此,数据量会大大减少,但同步事件的计数可能会被明显低估。
Java 程序的同步跟踪基于线程尝试获取 Java 监视器时生成的事件。对于这些事件,会同时收集机器调用栈和 Java 调用栈;但对于 JavaTM 虚拟机 (Java Virtual Machine, JVM) 软件中使用的内部锁定,不会收集任何同步跟踪数据。在机器表示法中,线程同步被移交给对 _lwp_mutex_lock 的调用,且不显示任何同步数据,因为没有跟踪这些调用。
表 2–2 同步等待跟踪度量
度量 |
定义 |
---|---|
对等待时间超过指定阈值的同步例程的调用数目。 |
|
超过指定阈值的等待时间的总和。 |
通过该信息,您可以确定函数或装入对象对同步例程进行调用时是会经常被阻塞还是会经历很长时间的等待。高同步等待时间表示线程间的争用。您可以通过重新设计算法,尤其是重新组织锁的结构,以便仅包含需要锁定的每个线程的数据来减少争用。
对未正确管理的内存分配和解除分配函数进行调用可能会造成数据的使用效率降低,从而导致应用程序的性能降低。在堆跟踪中,收集器通过插入 C 标准库内存分配函数 malloc、realloc、valloc 和 memalign 以及解除分配函数 free 跟踪内存分配和解除分配请求。对 mmap 的调用被视为内存分配,它允许记录 Java 内存分配的堆跟踪事件。由于 Fortran 函数 allocate 和 deallocate 调用 C 标准库函数,因此还会间接跟踪这些例程。
不支持对 Java 程序的堆分析。
表 2–3 内存分配(堆跟踪)度量
度量 |
定义 |
---|---|
分配数 |
对内存分配函数的调用数。 |
分配的字节 |
每次调用内存分配函数时分配的字节总数。 |
泄漏数 |
调用内存分配函数(未对解除分配函数进行相应的调用)的数量。 |
泄漏的字节 |
已分配但未解除分配的字节数。 |
收集堆跟踪数据有助于识别程序中的内存泄漏,或定位内存分配效率不高的位置。
内存泄漏的另一个常用定义(例如在 dbx 调试工具中)为:内存泄漏是一个动态分配的内存块,在程序的数据空间中没有任何指向它的指针。此处所使用的泄漏定义包括这种替换的定义,但也包括存在指针的内存。
收集器可以收集有关对消息传递接口 (Message Passing Interface, MPI) 库的调用的数据。目前,MPI 跟踪仅在运行 Solaris OS 的系统中可用。下面列出了用于收集数据的函数。
MPI_Allgather |
MPI_Allgatherv |
MPI_Allreduce |
MPI_Alltoall |
MPI_Alltoallv |
MPI_Barrier |
MPI_Bcast |
MPI_Bsend |
MPI_Gather |
MPI_Gatherv |
MPI_Irecv |
MPI_Isend |
MPI_Recv |
MPI_Reduce |
MPI_Reduce_scatter |
MPI_Rsend |
MPI_Scan |
MPI_Scatter |
MPI_Scatterv |
MPI_Send |
MPI_Sendrecv |
MPI_Sendrecv_replace |
MPI_Ssend |
MPI_Wait |
MPI_Waitall |
MPI_Waitany |
MPI_Waitsome |
MPI_Win_fence |
MPI_Win_lock |
表 2–4 MPI 跟踪度量
度量 |
定义 |
---|---|
MPI 接收数 |
接收数据的 MPI 函数中接收操作的数量 |
接收的 MPI 字节 |
MPI 函数中接收的字节数 |
MPI 发送数 |
发送数据的 MPI 函数中发送操作的数量 |
发送的 MPI 字节 |
MPI 函数中发送的字节数 |
MPI 时间 |
对 MPI 函数的所有调用所花费的时间 |
其他 MPI 调用 |
对其他 MPI 函数的调用数量 |
接收或发送时记录的字节数为调用中给定的缓冲区大小。此数字可能比接收或发送的实际字节数大。在全局通信函数和集合通信函数中,假定直接进行处理器间通信且未优化数据传输或重新传送数据,则发送或接收的字节数为最大数量。
表 2–5 中列出了被跟踪的 MPI 库的函数,分类为 MPI 发送函数、MPI 接收函数、MPI 发送和接收函数以及其他 MPI 函数。
表 2–5 MPI 函数分类为发送、接收、发送和接收以及其他
类别 |
函数 |
---|---|
MPI 发送函数 |
MPI_Bsend、MPI_Isend、MPI_Rsend、MPI_Send、MPI_Ssend |
MPI 接收函数 |
MPI_Irecv、MPI_Recv |
MPI 发送和接收函数 |
MPI_Allgather、MPI_Allgatherv、MPI_Allreduce、MPI_Alltoall、MPI_Alltoallv、MPI_Bcast、MPI_Gather、 MPI_Gatherv、MPI_Reduce、MPI_Reduce_scatter、MPI_Scan、MPI_Scatter、MPI_Scatterv、MPI_Sendrecv、MPI_Sendrecv_replace |
其他 MPI 函数 |
MPI_Barrier、MPI_Wait、MPI_Waitall 、MPI_Waitany、MPI_Waitsome、MPI_Win_fence、MPI_Win_lock |
收集 MPI 跟踪数据有助于标识 MPI 程序中可能因 MPI 调用而产生性能问题的位置。可能发生的性能问题的例子有负载平衡、同步延迟和通信瓶颈。
全局数据由收集器按名为样本包的包来记录。每个包中都包含一个数据包头、时间戳、内核的执行统计数据(如缺页和 I/O 数据)、上下文切换以及各种页面驻留(工作集和分页)统计数据。记录在样本包中的数据对程序来说是全局的,且不会转换为性能度量。记录样本包的过程称为抽样。
如果设置了有关停止的选项,而程序在 IDE 的“调试”窗口或 dbx 中因任何原因(如在断点处)停止时
选择了“调试”->“性能工具箱”->“启用收集器”,并选中“收集器”窗口中的“周期样本”复选框时
使用 dbx collector sample record 命令手动记录样本时
如果代码中包含对 collector_sample 的调用,则在调用该例程时(请参见数据收集的程序控制)
如果将 -l 选项与 collect 命令一起使用,则在传送指定信号时(请参见 collect(1) 手册页)
开始和终止收集时
使用 dbx collector pause 命令暂停收集(就在暂停之前)和使用 dbx collector resume 命令恢复收集时(就在恢复之后)
创建后续进程前后
性能工具使用记录在样本包中的数据,按时间周期将数据分组,这称为样本。您可以通过选择一组样本过滤特定事件的数据,以便只查看这些特定时间周期的信息。您也可以查看每个样本的全局数据。
性能工具不对不同种类的采样点进行区分。要利用采样点进行分析,您应只选择一种点进行记录。具体地说,如果要记录与程序结构或执行序列有关的采样点,则应关闭周期抽样,并使用在 dbx 停止进程,或将信号传送到正使用 collect 命令记录数据的进程,或调用收集器 API 函数时记录的样本。