对于大多数程序来说,您不必为数据收集和分析做任何特殊的准备。如果程序执行下列任一操作,则应当阅读下面的一个或多个小节:
安装信号处理程序
显式动态装入系统库
动态编译函数
创建要分析的后续进程
使用异步 I/O 库
直接使用分析计时器或硬件计数器 API
调用 setuid(2) 或执行 setuid 文件
此外,如果要控制程序中的数据收集,还应当阅读相关小节。
malloc、valloc 和 alloca (C/C++)
new (C++)
堆栈局部变量 (Fortran)
MALLOC 和 MALLOC64 (Fortran)
必须小心确保程序不依赖于动态分配的内存的初始内容,除非内存分配方法明确地说明要设置初始值:例如,比较 malloc(3C) 手册页中对 calloc 和 malloc 的描述。
偶尔,使用动态分配的内存的程序似乎可以单独地正常运行,但是启用性能数据收集之后就会失败。症状可能包括意外的浮点行为、段故障或特定于应用程序的错误消息。
如果应用程序单独运行时未初始化的内存偶然设置为良性值,但应用程序与性能数据收集工具一起运行时未初始化的内存被设置为其他值,则会出现这种行为。发生这种情况时,问题不出在性能工具上。依赖于动态分配的内存内容的任何应用程序都具有潜在的错误:除非明确说明使用其他方法,否则操作系统将为动态分配的内存随机提供任意内容。即使目前操作系统会始终将动态分配的内存设置为某个值,但是将来在使用操作系统的后续修订版或将程序移植到其他操作系统时,这些潜在的错误会引起意外的行为。
下列工具可以帮助您找到这些潜在的错误:
f95 -xcheck=init_local
有关更多信息,请参见《Fortran 用户指南》或 f95(1) 手册页
lint 实用程序
有关更多信息,请参见《C 用户指南》或 lint(1) 手册页
dbx 下的运行时检查
有关更多信息,请参见《使用 dbx 调试程序》手册或 dbx(1) 手册页。
Rational Purify
收集器插入各种系统库的函数,以收集跟踪数据并确保数据收集的完整性。下面的列表描述了收集器插入库函数调用的情况。
收集同步等待跟踪数据。收集器插入 Solaris 线程库 libthread.so(在 Solaris 9 OS 上)和 Solaris C 库 libc.so(在 Solaris 10 OS 上)中的函数。
收集堆跟踪数据。收集器插入函数 malloc、realloc、memalign 和 free。这些函数的版本可以在 C 标准库 libc.so 和其他库(如 libmalloc.so 和 libmtmalloc.so)中找到。
确保时钟数据的完整性。收集器插入 setitimer 并阻止程序使用分析计时器。
确保硬件计数器数据的完整性。收集器插入硬件计数器库 libcpc.so 中的函数并阻止程序使用计数器。程序对该库中函数的调用的返回值是 -1。
针对后续进程启用数据收集。收集器插入函数 fork(2)、fork1(2)、vfork(2)、fork(3F)、system(3C)、system(3F)、sh(3F)、popen(3C) 和 exec(2) 及其变体。对 vfork 的调用已在内部被替换为对 fork1 的调用。这些插入仅适用于 collect 命令。
保证由收集器处理 SIGPROF 和 SIGEMT 信号。收集器插入 sigaction 以确保其信号处理程序是这些信号的主信号处理程序。
在某些情况下,插入不会成功:
将程序与任何包含被插入的函数的库进行静态链接。
将 dbx 附加到运行中的未预装入收集器库的应用程序。
动态装入其中一个库并通过只在该库中搜索来解析符号。
收集器插入失败可能会导致性能数据丢失或无效。
收集器使用以下两个信号来收集分析数据:SIGPROF(所有实验)和 SIGEMT(仅限硬件计数器实验)。收集器为其中的每个信号安装一个信号处理程序。该信号处理程序截获并处理它自己的信号,但是会将其他信号传递到所安装的其他信号处理程序。如果程序为这些信号安装其自己的信号处理程序,则收集器会将其信号处理程序作为主处理程序重新安装,以保证性能数据的完整性。
collect 命令还可以将用户指定的信号用于暂停和恢复数据收集以及记录样本。尽管在安装用户处理程序时向实验中写入警告,但这些信号不受收集器保护。确保收集器对指定信号的使用与应用程序对相同信号的使用之间没有冲突是您的责任。
由收集器安装的信号处理程序会设置一个确保系统调用不被信号传送中断的标志。如果程序的信号处理程序将该标志设置为允许中断系统调用,则设置该标志可以更改程序的行为。在异步 I/O 库 libaio.so 中就有一个行为更改的重要示例,它将 SIGPROF 用于异步取消操作,并且中断系统调用。如果已安装收集器库 libcollector.so,则取消信号总是来得太迟,以至于无法取消异步 I/O 操作。
如果在未预装入收集器库和启用性能数据收集的情况下将 dbx 附加到进程,并且程序随后安装其自身的信号处理程序,则收集器不再重新安装其自身的信号处理程序。在这种情况下,程序的信号处理程序必须确保 SIGPROF 和 SIGEMT 信号被传递,以便性能数据不丢失。如果程序的信号处理程序中断系统调用,那么程序行为和分析行为都将与预装入收集器库时不同。
由于动态装入器实施了一定的限制,因此将难以使用 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++ 接口:
第一种方法是包括 collectorAPI.h 并使用 -lcollectorAPI(包含用于检查底层 libcollector.so API 函数是否存在的真正的函数)进行链接。
这种方法要求与 API 库相链接,可在所有情况下使用。如果没有活动的实验,API 调用将被忽略。
第二种方法是包括 libcollector.h(包含用于检查基础 libcollector.so API 函数是否存在的宏)。
当用于主可执行文件以及数据收集功能随程序一起启动时,该方法有效。当 dbx 用于附加到进程或者从进程针对其执行 dlopen 的共享库中使用时,该方法不总是有效。提供第二种方法的目的仅在于实现向后兼容,建议不要将它用于任何其他目的。
请勿使用 -lcollector 链接任何语言的程序,否则,收集器可能会出现不可预知的行为。
Fortran API libfcollector.h 文件定义了库的 Fortran 接口。要使用该库,必须使用 -lcollectorAPI 链接应用程序。(还提供了该库的替代名称 -lfcollector,目的在于实现向后兼容性。)除动态函数、线程暂停和恢复调用等功能外,Fortran API 提供了与 C 和 C++ API 相同的功能。
要使用 Fortran 的 API 函数,请插入下面的语句:
include "libfcollector.h" |
请勿使用 -lcollector 链接任何语言的程序,否则,收集器可能会出现不可预知的行为。
使用以下语句导入 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() 函数。可通过两种方法来使用这些函数:一种是使用一个主线程为所有线程(包括其自身)执行调用;另一种是让每个线程只执行它自己的调用。其他任何用法都可能产生不可预知的结果。
对 API 函数的描述如下所示。
C 和 C++:collector_sample(char *name)
Fortran: collector_sample(string name)
Java:CollectorAPI.sample(String name)
记录样本包并用指定的名称或字符串标记该样本。该标签显示在性能分析器的“事件”选项卡中。Fortran 参数 string 的类型为 character。
样本点包含进程(而不是单独的线程)的数据。在多线程应用程序中,如果在 collector_sample() API 函数记录样本时发生另一个调用,则该函数可确保只写入一个样本。所记录的样本数可能会少于发出该调用的线程数。
性能分析器不对由不同机制记录的样本进行区分。如果只想查看 API 调用所记录的样本,则应当在记录性能数据时关闭所有其他抽样模式。
C、C++ 和 Fortran:collector_pause()
Java:CollectorAPI.pause()
停止将特定于事件的数据写入实验。实验将保持打开状态,并将继续写入全局数据。如果没有活动的实验或者已经停止记录数据,则该调用将被忽略。该函数停止写入所有特定于事件的数据,即使它是由 collector_thread_resume() 函数针对特定线程启用的也是如此。
C、C++ 和 Fortran:collector_resume()
Java:CollectorAPI.resume()
在调用 collector_pause() 之后恢复将特定于事件的数据写入实验。如果没用活动的实验或数据记录功能处于活动状态,则该调用将被忽略。
仅限 C 和 C++:collector_thread_pause(unsigned int t)
Java:CollectorAPI.threadPause(Thread t)
停止将特定于事件的数据从参数列表中所指定的线程写入实验。对于 C/C++ 程序,参数 t 是 POSIX 线程标识符;对于 Java 程序,该参数是 Java 线程。如果实验已经终止、没有活动的实验或已经关闭对该线程数据的写入功能,则该调用将被忽略。即使全局启用了数据写入功能,该函数也会停止写入指定线程的数据。缺省情况下,对单独线程的数据记录功能处于打开状态。
仅限 C 和 C++:collector_thread_resume(unsigned int t)
Java:CollectorAPI.threadResume(Thread t)
恢复将参数列表中指定线程的特定于事件的数据写入实验。对于 C/C++ 程序,参数 t 是 POSIX 线程标识符;对于 Java 程序,该参数是 Java 线程。如果实验已经终止、没有活动的实验或对该线程数据的写入功能已经打开,则该调用将被忽略。只有在全局启用了数据写入功能,而且还针对该线程启用了数据写入功能时,才可以将数据写入实验中。
C、C++ 和 Fortran:collector_terminate_expt()
Java:CollectorAPI.terminate
如果 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 编译方法的函数数据和带注释的反汇编列表,但不能查看带注释的源代码列表。
将有关动态编译的函数的信息传递到收集器,以便在实验中进行记录。下表对参数列表进行了描述。
表 3–1 collector_func_load() 的参数列表
参数 |
定义 |
---|---|
name |
性能工具所使用的动态编译函数的名称。该名称不必是函数的实际名称。虽然该名称不应包含嵌入的空格或引号字符,但无须遵循通常的函数命名约定。 |
alias |
用于描述函数的任意字符串。它可以是 NULL。它不经过任何方式的解释,可以包含嵌入的空格。它显示在分析器的“摘要”选项卡中。它可用于指示函数的内容或动态构造函数的原因。 |
sourcename |
构造函数时所在源文件的路径。它可以是 NULL。该源文件用于带注释的源代码列表。 |
vaddr |
函数的装入地址。 |
size |
以字节为单位的函数大小。 |
lntsize |
对行号表中条目数量的计数。如果未提供行号信息,则计数应为零。 |
lntable |
包含 lntsize 条目的表,其中每个条目都是一对整数。第一个整数是偏移量,第二个整数是行号。在一个条目的偏移量和下一个条目中所给出的偏移量之间的所有指令都归属于在第一个条目中提供的行号。偏移量必须按数字升序列出,但行号的顺序可以是任意的。如果 lntable 为 NULL,则没有可用的函数源代码列表,不过反汇编列表是可用的。 |
通知收集器位于地址 vaddr 的动态函数已卸载。