每个事件的数据都包含高精度时间戳、线程 ID 和 CPU ID。这些项可用于在性能分析器中按时间、线程或 CPU 过滤度量。有关 CPU ID 的信息,请参见 getcpuid(2) 手册页。在 getcpuid 不可用的系统上,处理器 ID 为 -1(它映射为 "Unknown"(未知))。
除了通用数据外,每个事件还生成特定的原始数据,将在以下各节中对此进行描述。每节还将介绍从原始数据派生的度量的准确性,以及数据收集对度量的影响。
时钟分析的事件特定数据由分析间隔计数的数组组成。 在 Oracle Solaris 上,提供了间隔计数器。在分析间隔结束时,相应的间隔计数器加 1,并安排另一个分析信号。仅当 Solaris 线程进入 CPU 用户模式时,才记录和重置数组。重置数组包括将用户 CPU 状态的数组元素设置为 1,将所有其他状态的数组元素设置为 0。在重置数组之前,进入用户模式时会记录数组数据。因此,该数组包含自上次进入用户模式以来进入的每个微状态的计数累积(内核为每个 Solaris 线程维护十个微状态)。在 Linux 操作系统上,不存在微状态;唯一的间隔计数器是用户 CPU 时间。
在记录数据的同时记录调用堆栈。如果在分析间隔结束时 Solaris 线程未处于用户模式,则在线程再次进入用户模式之前调用堆栈无法更改。因此,调用堆栈总是会在每个分析间隔结束时准确记录程序计数器的位置。
表 13中显示了 Oracle Solaris 上的每个微状态提供的度量。
|
计时数据是基于统计收集的,因此易于出现任何统计抽样方法的所有误差。对于时间非常短的运行(仅记录少量分析数据包),调用堆栈可能不能表示程序中使用大多数资源的各部分。因此应以足够长的时间或足够多的次数运行程序,以累积感兴趣的函数或源代码行的数百个分析数据包。
除了统计抽样误差外,收集和归属数据的方式以及程序在系统中前进方式也会引起特定的误差。以下是计时度量可能出现不准确或失真的一些情况:
创建线程时,记录第一个分析数据包之前所用的时间少于分析间隔,但是整个分析间隔取决于第一个分析数据包中记录的微状态。如果创建了许多线程,则误差可能是分析间隔的许多倍。
销毁线程时,在记录最后一个分析数据包后会消耗一些时间。如果销毁了许多线程,则误差可能是分析间隔的许多倍。
在分析间隔期间可能发生线程的重新调度。因此,记录的线程状态可能不能表示消耗大部分分析间隔的微状态。如果要运行的线程多于要运行它们的处理器,则误差很可能更大。
程序可以与系统时钟相关联的方式运行。 在这种情况下,如果线程处于可能表示所用的一小部分时间的状态,而为特定程序部分记录的调用堆栈被过度表示,则分析间隔始终会过期。在多处理器系统上,分析信号可以引入相关性:记录微状态时,在运行程序的线程时由分析信号中断的处理器很可能处于陷阱 CPU 微状态。
分析间隔过期时,内核记录微状态值。如果系统负载过重,则该值可能无法表示进程的真实状态。在 Oracle Solaris 上,此情况很可能会导致对陷阱 CPU 或等待 CPU 微状态的过多记帐。
系统时钟与外部源同步时,在分析数据包中记录的时间戳不反映分析间隔,但包括对时钟进行的任何调整。时钟调整可能使分析数据包看起来已丢失。涉及的时间段通常为几秒,并且调整是以增量方式进行的。
在动态更改其操作时钟频率的计算机上所记录的实验可能在分析中显示不准确之处。
除刚刚介绍的不准确之处外,计时度量还会因收集数据的过程而失真。 记录分析数据包所用的时间从不出现在程序的度量中,因为记录是由分析信号启动的。(这是相关性的另一个实例。记录过程中所用的用户 CPU 时间在所记录的任何微状态之间分配。结果是对用户 CPU 时间度量过少计数,而对其他度量过多计数。记录数据所用的时间量通常不到缺省分析间隔的 CPU 时间的百分之几。
如果将通过在基于时钟的实验中进行分析所获得的计时度量与通过其他方式获得的时间进行比较,则应该注意以下问题。
对于单线程应用程序,为进程记录的总线程时间通常精确到千分之几(与同一进程的 gethrtime(3C) 返回的值相比)。 CPU 时间可能与由同一进程的 gethrvtime(3C) 返回的值相差几个百分点。如果负载过重,则差异可能更加明显。但是,CPU 时间差异并不表示系统失真,为不同函数、源代码行等报告的相对时间不会显著失真。
性能分析器中报告的线程时间可能与 vmstat 报告的时间有很大差异,因为 vmstat 报告的是各 CPU 的线程时间总和。如果目标进程具有的 LWP 比它所运行的系统具有的 CPU 多,则性能分析器显示的等待时间比 vmstat 报告的长。
出现在性能分析器的 "Statistics"(统计信息)视图和 er_print 统计信息显示中的微状态计时基于进程文件系统 /proc 使用报告,因此微状态中所用时间的记录具有很高的准确性。有关更多信息,请参见 proc (4) 手册页。可以将这些计时与 <Total> 函数(它将程序作为一个整体表示)的度量进行比较,以获取聚集计时度量的准确性指示。 但是,"Statistics"(统计信息)视图中显示的值可能包含其他基值,而 <Total> 的计时度量值中不包含这些基值。这些基值来自暂停数据收集的时间段。
用户 CPU 时间和硬件计数器循环时间是不同的,因为在将 CPU 模式切换到系统模式时会关闭硬件计数器。有关更多信息,请参见陷阱。
硬件计数器溢出分析数据包括计数器 ID 和溢出值。 该值可能大于计数器的溢出设置值,因为处理器在事件的溢出和记录之间会执行某些指令。尤其对循环和指令计数器来说,该值可能会更大,这些计数器的递增频率比诸如浮点运算或高速缓存未命中次数的计数器更快。记录事件中的延迟还意味着,通过调用堆栈记录的程序计数器地址并不精确对应于溢出事件。有关更多信息,请参见硬件计数器溢出的归属。另请参见陷阱的讨论。陷阱和陷阱处理程序可以导致报告的用户 CPU 时间和循环计数器报告的时间有很大差别。
在动态更改其操作时钟频率的计算机上所记录的实验在基于周期的时间计数转换时可能显示不准确之处。
收集数据所用的时间可能是程序执行时间的很大一部分。收集运行可能要用大部分时间来处理溢出和写入数据而不是运行程序。
计数的很大一部分可能来自收集过程。这些计数归属到收集器函数 collector_record_counters。如果看到此函数的计数很大,则表明溢出值过小。
数据的收集可以更改程序的行为。例如,如果要收集有关高速缓存未命中次数的数据,则大多数未命中可能是由刷新收集器指令以及分析高速缓存中的数据并将其替换为程序指令和数据所导致的。程序的高速缓存未命中次数看上去似乎很大,但是如果没有数据收集,则实际上高速缓存未命中次数可能非常小。
在内存空间分析中,将针对计算机的物理结构(例如,高速缓存行、内存区或页面)报告内存相关事件(例如,高速缓存未命中次数)。
在数据空间分析中,将针对导致事件的数据结构引用而非仅针对发生内存相关事件的指令报告内存相关事件。数据空间分析仅在运行 Oracle Solaris 的 SPARC 系统上可用。它在运行 Oracle Solaris 或 Linux 的 x86 系统上尚不可用。
对于内存空间或数据空间分析,收集的数据必须是使用基于内存的计数器的硬件计数器分析。对于 SPARC 平台或 x86 Oracle Solaris 平台上的精确计数器,缺省情况下将收集内存空间和数据空间数据。
为了支持数据库空间分析,应使用 -xhwcprof 标志编译可执行文件。此标志适用于使用 C、C++ 和 Fortran 编译器进行编译,但仅在 SPARC 平台上有意义。在其他平台上将忽略该标志。如果不使用 -xhwcprof 编译可执行文件,er_print 中的 data_layout、data_single 和 data_objects 命令将不显示数据。数据空间分析无需针对精确计数器使用 -xhwcprof。
当实验包含数据空间或内存空间分析时,er_print 实用程序允许执行三条附加命令:data_objects、data_single 和 data_layout 以及与内存对象相关的各种命令。有关更多信息,请参见控制数据空间列表的命令。
此外,性能分析器包括两个与数据空间分析相关的视图,以及用于内存对象的各种标签。请参见 "DataObjects"(数据对象)视图、"DataLayout"(数据布局)视图 和 "MemoryObjects"(内存对象)视图。
运行不带其他参数的 collect -h 将列出硬件计数器,并指明这些计数器是否与装入、存储或装入存储相关以及它们是否精确。请参见硬件计数器分析数据。
收集器通过跟踪对线程库 libthread.so 中函数的调用或对实时扩展库 librt.so 的调用来收集同步延迟事件。 事件特定的数据由请求和授权的高精度时间戳(跟踪的调用的开始和结束)以及同步对象(例如,请求的互斥锁)的地址组成。 线程 ID 和 LWP ID 是记录数据时的 ID。等待时间是请求时间和授权时间之间的差值。 仅记录其等待时间超过指定阈值的事件。同步等待跟踪数据在授权时记录在实验中。
等待线程不能执行任何其他工作,直到导致延迟的事件完成为止。等待所用时间同时显示为同步等待时间和用户锁定时间。用户锁定时间可能比同步等待时间长,因为同步延迟阈值筛去了短期延迟。
数据收集的开销使等待时间失真。该开销与收集的事件数成比例。通过增加用于记录事件的阈值,可以最大限度地减少开销中的等待时间部分。
收集器通过插入内存分配和取消分配函数 malloc、realloc、memalign 和 free 来记录对这些函数的调用的跟踪数据。如果程序分配内存时忽视这些函数,则不记录跟踪数据。不记录 Java 内存管理(它使用不同的机制)的跟踪数据。
跟踪的函数可能从许多库中的任一个库装入。在性能分析器中看到的数据可能取决于给定函数从哪个库装入。
如果程序在很短的时间段内发出对被跟踪函数的大量调用,则执行程序所用的时间可能会大大延长。额外的时间将用于记录跟踪数据。
收集器可记录对标准 I/O 例程的调用和所有 I/O 系统调用的跟踪数据。
MPI 跟踪基于修改的 VampirTrace 数据收集器。有关更多信息,请在 Technische Universität Dresden Web 站点上搜索《VampirTrace User Manual》(《VampirTrace 用户手册》)。