Sun Studio 12:性能分析器

为数据收集和分析准备程序

对于大多数程序来说,您不必为数据收集和分析做任何特殊的准备。如果程序执行下列任一操作,则应当阅读下面的一个或多个小节:

此外,如果要控制程序中的数据收集,还应当阅读相关小节。

使用动态分配的内存

许多程序依赖于动态分配的内存,它们使用诸如以下各项的功能:

必须小心确保程序不依赖于动态分配的内存的初始内容,除非内存分配方法明确地说明要设置初始值:例如,比较 malloc(3C) 手册页中对 callocmalloc 的描述。

偶尔,使用动态分配的内存的程序似乎可以单独地正常运行,但是启用性能数据收集之后就会失败。症状可能包括意外的浮点行为、段故障或特定于应用程序的错误消息。

如果应用程序单独运行时未初始化的内存偶然设置为良性值,但应用程序与性能数据收集工具一起运行时未初始化的内存被设置为其他值,则会出现这种行为。发生这种情况时,问题不出在性能工具上。依赖于动态分配的内存内容的任何应用程序都具有潜在的错误:除非明确说明使用其他方法,否则操作系统将为动态分配的内存随机提供任意内容。即使目前操作系统会始终将动态分配的内存设置为某个值,但是将来在使用操作系统的后续修订版或将程序移植到其他操作系统时,这些潜在的错误会引起意外的行为。

下列工具可以帮助您找到这些潜在的错误:

使用系统库

收集器插入各种系统库的函数,以收集跟踪数据并确保数据收集的完整性。下面的列表描述了收集器插入库函数调用的情况。

在某些情况下,插入不会成功:

收集器插入失败可能会导致性能数据丢失或无效。

使用信号处理程序

收集器使用以下两个信号来收集分析数据:SIGPROF(所有实验)和 SIGEMT(仅限硬件计数器实验)。收集器为其中的每个信号安装一个信号处理程序。该信号处理程序截获并处理它自己的信号,但是会将其他信号传递到所安装的其他信号处理程序。如果程序为这些信号安装其自己的信号处理程序,则收集器会将其信号处理程序作为主处理程序重新安装,以保证性能数据的完整性。

collect 命令还可以将用户指定的信号用于暂停和恢复数据收集以及记录样本。尽管在安装用户处理程序时向实验中写入警告,但这些信号不受收集器保护。确保收集器对指定信号的使用与应用程序对相同信号的使用之间没有冲突是您的责任。

由收集器安装的信号处理程序会设置一个确保系统调用不被信号传送中断的标志。如果程序的信号处理程序将该标志设置为允许中断系统调用,则设置该标志可以更改程序的行为。在异步 I/O 库 libaio.so 中就有一个行为更改的重要示例,它将 SIGPROF 用于异步取消操作,并且中断系统调用。如果已安装收集器库 libcollector.so则取消信号总是来得太迟,以至于无法取消异步 I/O 操作。

如果在未预装入收集器库和启用性能数据收集的情况下将 dbx 附加到进程,并且程序随后安装其自身的信号处理程序,则收集器不再重新安装其自身的信号处理程序。在这种情况下,程序的信号处理程序必须确保 SIGPROFSIGEMT 信号被传递,以便性能数据不丢失。如果程序的信号处理程序中断系统调用,那么程序行为和分析行为都将与预装入收集器库时不同。

使用 setuid

由于动态装入器实施了一定的限制,因此将难以使用 setuid(2) 和收集性能数据。如果您的程序调用 setuid或执行 setuid 文件,则收集器可能无法写入实验文件,原因是它缺少新用户 ID 的必需权限。

此问题可以通过以下方法来解决:确保 umask 的设置可将写入权限授予进程在运行时所使用的任何 UID 或 GID。针对实验,这些 ID 必须具有写入权限。

数据收集的程序控制

如果要控制程序中的数据收集,收集器共享库 libcollector.so 包含了一些可以使用的 API 函数。这些函数是用 C 编写的,还提供了一个 Fortran 接口。C 接口和 Fortran 接口都是在由库所提供的头文件中定义的。

API 函数定义如下所示。


void collector_sample(char *name);
void collector_pause(void);
void collector_resume(void);
void collector_thread_pause(unsigned int t);
void collector_thread_resume(unsigned int t);
void collector_terminate_expt(void);

CollectorAPI 类为 JavaTM 程序提供了类似的功能,Java 接口中对其进行了介绍。

C 和 C++ 接口

可通过两种方法来访问 C 和 C++ 接口:

Fortran 接口

Fortran API libfcollector.h 文件定义了库的 Fortran 接口。要使用该库,必须使用 -lcollectorAPI 链接应用程序。(还提供了该库的替代名称 -lfcollector,目的在于实现向后兼容性。)除动态函数、线程暂停和恢复调用等功能外,Fortran API 提供了与 C 和 C++ API 相同的功能。

要使用 Fortran 的 API 函数,请插入下面的语句:


include "libfcollector.h"

注 –

请勿使用 -lcollector 链接任何语言的程序,否则,收集器可能会出现不可预知的行为。


Java 接口

使用以下语句导入 CollectorAPI 类并访问 Java API。但是请注意,必须使用指向 / installation_directory/lib/collector.jar 的类路径来调用应用程序,其中 installation-directory 是 Sun Studio 软件的安装目录。


import com.sun.forte.st.collector.CollectorAPI;

Java CollectorAPI 方法的定义如下所示:


CollectorAPI.sample(String name)
CollectorAPI.pause()
CollectorAPI.resume()
CollectorAPI.threadPause(Thread thread)
CollectorAPI.threadResume(Thread thread)
CollectorAPI.terminate()

除动态函数 API 之外,Java API 包含与 C 和 C++ API 相同的函数。

C 头文件 libcollector.h 包含一些宏,这些宏的作用是如果当时未在收集数据,则跳过对真正的 API 函数的调用。在这种情况下,不动态装入函数。但是,由于在某些情况下这些宏不能很好地运行,所以使用这些宏会有风险。使用 collectorAPI.h 较为安全,因为它不使用宏,而是直接引用函数。

如果正在收集性能数据,则 Fortran API 子例程会调用 C API 函数,否则这些子例程将返回。检查的开销很低,不会对程序性能产生太大的影响。

如本章稍后所述,要收集性能数据就必须使用收集器运行您的程序。插入对 API 函数的调用不会启用数据收集功能。

如果要在多线程程序中使用 API 函数,应当确保它们只由一个线程调用。除 collector_thread_pause()collector_thread_resume() 之外,API 函数执行适用于进程(而不是单独的线程)的操作。如果每个线程都调用 API 函数,则记录的数据可能会与预期不同。例如,如果一个线程在其他线程到达程序中的同一点之前调用了 collector_pause()collector_terminate_expt(),则会针对所有线程暂停或终止收集,从而丢失那些正在执行 API 调用之前代码的线程的数据。要在单独的线程级别控制数据收集,请使用 collector_thread_pause()collector_thread_resume() 函数。可通过两种方法来使用这些函数:一种是使用一个主线程为所有线程(包括其自身)执行调用;另一种是让每个线程只执行它自己的调用。其他任何用法都可能产生不可预知的结果。

C、C++、Fortran 和 Java API 函数

对 API 函数的描述如下所示。

动态函数和模块

如果 C 或 C++ 程序向程序的数据空间动态编译函数,而且您希望在性能分析器中查看动态函数或模块的数据,那么,您必须向收集器提供信息。该信息由对收集器 API 函数的调用传递。API 函数的定义如下所示。


void collector_func_load(char *name, char *alias,
    char *sourcename, void *vaddr, int size, int lntsize,
    Lineno *lntable);
void collector_func_unload(void *vaddr);

您不必将这些 API 函数用于由 Java HotSpotTM 虚拟机编译的 Java 方法,该虚拟机使用的是另一个接口。Java 接口提供已编译到收集器的方法的名称。您可以查看 Java 编译方法的函数数据和带注释的反汇编列表,但不能查看带注释的源代码列表。

对 API 函数的描述如下所示。

collector_func_load()

将有关动态编译的函数的信息传递到收集器,以便在实验中进行记录。下表对参数列表进行了描述。

表 3–1 collector_func_load() 的参数列表

参数 

定义 

name

性能工具所使用的动态编译函数的名称。该名称不必是函数的实际名称。虽然该名称不应包含嵌入的空格或引号字符,但无须遵循通常的函数命名约定。 

alias

用于描述函数的任意字符串。它可以是 NULL。它不经过任何方式的解释,可以包含嵌入的空格。它显示在分析器的“摘要”选项卡中。它可用于指示函数的内容或动态构造函数的原因。

sourcename

构造函数时所在源文件的路径。它可以是 NULL。该源文件用于带注释的源代码列表。

vaddr

函数的装入地址。 

size

以字节为单位的函数大小。 

lntsize

对行号表中条目数量的计数。如果未提供行号信息,则计数应为零。 

lntable

包含 lntsize 条目的表,其中每个条目都是一对整数。第一个整数是偏移量,第二个整数是行号。在一个条目的偏移量和下一个条目中所给出的偏移量之间的所有指令都归属于在第一个条目中提供的行号。偏移量必须按数字升序列出,但行号的顺序可以是任意的。如果 lntableNULL,则没有可用的函数源代码列表,不过反汇编列表是可用的。

collector_func_unload()

通知收集器位于地址 vaddr 的动态函数已卸载。