Oracle® Developer Studio 12.5:性能分析器

退出打印视图

更新时间: 2016 年 6 月
 
 

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

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

此外,如果要在运行时从程序控制数据收集,请参阅使用 libcollector 库从程序控制数据收集

使用动态分配的内存

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

  • mallocvallocalloca (C/C++)

  • new (C++)

  • 堆栈局部变量 (Fortran)

  • MALLOCMALLOC64 (Fortran)

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

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

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

使用系统库

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

  • 收集同步等待跟踪数据。在 Oracle Solaris 上,收集器插入 Oracle Solaris C 库 libc.so 中的函数。

  • 收集 I/O 跟踪数据。收集器会插入在以下函数上:

    /* interposition function handles */
    static int (*__real_open)(const char *path, int oflag, ...) = NULL;
    #if WSIZE(32)
    static int (*__real_open64)(const char *path, int oflag, ...) = NULL;
    #endif
    static int (*__real_fcntl)(int fildes, int cmd, ...) = NULL;
    static int (*__real_openat)(int fildes, const char *path, int oflag, ...) = NULL;
    static int (*__real_close)(int fildes) = NULL;
    static FILE *(*__real_fopen)(const char *filename, const char *mode) = NULL;
    static int (*__real_fclose)(FILE *stream) = NULL;
    static int (*__real_dup)(int fildes) = NULL;
    static int (*__real_dup2)(int fildes, int fildes2) = NULL;
    static int (*__real_pipe)(int fildes[2]) = NULL;
    static int (*__real_socket)(int domain, int type, int protocol) = NULL;
    static int (*__real_mkstemp)(char *template) = NULL;
    static int (*__real_mkstemps)(char *template, int slen) = NULL;
    static int (*__real_creat)(const char *path, mode_t mode) = NULL;
    
    #if WSIZE(32)
    static int (*__real_creat64)(const char *path, mode_t mode) = NULL;
    #endif
    static FILE *(*__real_fdopen)(int fildes, const char *mode) = NULL;
    
    static ssize_t (*__real_read)(int fildes, void *buf, size_t nbyte) = NULL;
    static ssize_t (*__real_write)(int fildes, const void *buf, size_t nbyte) = NULL;
    static ssize_t (*__real_readv)(int fildes, const struct iovec *iov, int iovcnt) = NULL;
    static ssize_t (*__real_writev)(int fildes, const struct iovec *iov, int iovcnt) = NULL;
    static size_t (*__real_fread)(void *ptr, size_t size, size_t nitems, FILE *stream) = NULL;
    static size_t (*__real_fwrite)(void *ptr, size_t size, size_t nitems, FILE *stream) = NULL;
    static ssize_t (*__real_pread)(int fildes, void *buf, size_t nbyte, off_t offset) = NULL;
    static ssize_t (*__real_pwrite)(int fildes, const void *buf, size_t nbyte, off_t offset) = NULL;
    #if OS(Linux)
    static ssize_t (*__real_pwrite64)(int fildes, const void *buf, size_t nbyte, off64_t offset) = NULL;
    #endif
    static char *(*__real_fgets)(char *s, int n, FILE *stream) = NULL;
    static int (*__real_fputs)(const char *s, FILE *stream) = NULL;
    static int (*__real_fputc)(int c, FILE *stream) = NULL;
    static int (*__real_fprintf)(FILE *stream, const char *format, ...) = NULL;
    static int (*__real_vfprintf)(FILE *stream, const char *format, va_list ap) = NULL;
    
    static off_t (*__real_lseek)(int fildes, off_t offset, int whence) = NULL;
    static offset_t (*__real_llseek)(int fildes, offset_t offset, int whence) = NULL;
    static int (*__real_chmod)(const char *path, mode_t mode) = NULL;
    static int (*__real_access)(const char *path, int amode) = NULL;
    static int (*__real_rename)(const char *old, const char *new) = NULL;
    static int (*__real_mkdir)(const char *path, mode_t mode) = NULL;
    static int (*__real_getdents)(int fildes, struct dirent *buf, size_t nbyte) = NULL;
    static int (*__real_unlink)(const char *path) = NULL;
    static int (*__real_fseek)(FILE *stream, long offset, int whence) = NULL;
    static void (*__real_rewind)(FILE *stream) = NULL;
    static long (*__real_ftell)(FILE *stream) = NULL;
    static int (*__real_fgetpos)(FILE *stream, fpos_t *pos) = NULL;
    static int (*__real_fsetpos)(FILE *stream, const fpos_t *pos) = NULL;
    #if WSIZE(32)
    static int (*__real_fgetpos64)(FILE *stream, fpos64_t *pos) = NULL;
    static int (*__real_fsetpos64)(FILE *stream, const fpos64_t *pos) = NULL;
    #endif // WSIZE(32)
    static int (*__real_fsync)(int fildes) = NULL;
    static struct dirent *(*__real_readdir)(DIR *dirp) = NULL;
    #if OS(Linux)
    static int (*__real_flock)(int fd, int operation) = NULL;
    #endif
    static int (*__real_lockf)(int fildes, int function, off_t size) = NULL;
    static int (*__real_fflush)(FILE *stream) = NULL;
    #if OS(Linux) && ARCH(Intel) && WSIZE(32)
    static FILE *(*__real_fopen_2_1)(const char *filename, const char *mode) = NULL;
    static int (*__real_fclose_2_1)(FILE *stream) = NULL;
    static FILE *(*__real_fdopen_2_1)(int fildes, const char *mode) = NULL;
    static int (*__real_fgetpos_2_2)(FILE *stream, fpos_t *pos) = NULL;
    static int (*__real_fsetpos_2_2)(FILE *stream, const fpos_t *pos) = NULL;
    static FILE *(*__real_fopen_2_0)(const char *filename, const char *mode) = NULL;
    static int (*__real_fclose_2_0)(FILE *stream) = NULL;
    static FILE *(*__real_fdopen_2_0)(int fildes, const char *mode) = NULL;
    static int (*__real_fgetpos_2_0)(FILE *stream, fpos_t *pos) = NULL;
    static int (*__real_fsetpos_2_0)(FILE *stream, const fpos_t *pos) = NULL;
    #endif
  • 收集堆跟踪数据。收集器插入函数 mallocreallocmemalignfree。这些函数的版本可以在 C 标准库 libc.so 和其他库(如 libumen.solibmalloc.solibmtmalloc.so)中找到。

  • 收集 MPI 跟踪数据。收集器从指定的 MPI 库插入函数。

  • 确保时钟数据的完整性。收集器插入 setitimer 并阻止程序使用分析计时器。

  • 确保硬件计数器数据的完整性。收集器插入硬件计数器库 libcpc.so 中的函数并阻止程序使用计数器。 程序对该库中函数的调用的返回值是 -1

  • 针对子孙进程启用数据收集。收集器插入函数 fork(2)、fork1(2)、vfork(2)、fork(3F)、posix_spawn(3p)、posix_spawnp(3p)、 system(3C)、system(3F)、sh(3F)、popen(3C) 和 exec(2) 及其变体。对 vfork 的调用已在内部被替换为对 fork1 的调用。这些插入适用于 collect 命令。

  • 保证由收集器处理 SIGPROFSIGEMT 信号。收集器插入 sigaction 以确保其信号处理程序是这些信号的主信号处理程序。

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

  • 将程序与任何包含被插入的函数的库进行静态链接。

  • dbx 附加到运行中的未预装入收集器库的应用程序。

  • 动态装入其中一个库并通过只在该库中搜索来解析符号。

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

er_sync.soer_iotrace.soer_heap.soer_mpviewn.so(其中 n 表示 MPI 版本)库仅在分别请求同步等待跟踪数据、I/O 跟踪数据、堆跟踪数据或 MPI 跟踪数据时装入。

数据收集和信号

信号同时用于时钟分析和硬件计数器分析。SIGPROF 用于所有实验的数据收集。生成信号的周期取决于收集的数据。SIGEMT(在 Solaris 上)或 SIGIO(在 Linux 上)用于硬件计数器分析。溢出间隔取决于要分析的用户参数。任何使用或处理分析信号的用户代码都可能会干扰数据收集。当收集器安装用于分析信号的信号处理程序时,它会设置一个标志来确保系统调用不被中断以传送信号。此设置可能会更改将分析信号用于其他用途的目标程序的行为。

当收集器为分析信号安装其信号处理程序时,它会记住目标是否安装了自己的信号处理程序。收集器还会插入到某些信号处理例程且不允许用户为这些信号安装信号处理程序;它会保存用户的处理程序,就像收集器在启动实验时替换用户处理程序一样。

分析信号由内核中的分析计时器或硬件计数器溢出处理代码提供,或在对 kill(2)、sigsend(2) tkill(2)、tgkill(2)_lwp_kill(2) 系统调用、raise(3C)sigqueue(3C) 库调用或 kill 命令的响应中提供。会随信号提供信号代码,以便收集器能区分源。如果是针对分析提供的,收集器会处理该代码;如果不是针对分析提供的,则会将其提供给目标信号处理程序。

dbx 下运行收集器时,提供的分析信号的信号代码有时会被损坏,此时会当作分析信号是从系统或库调用或某个命令生成的。在这种情况下,会错误地将其提供给用户的处理程序。如果用户处理程序设置为 SIG_DFL,将导致进程的信息转储失败。

在连接到目标进程后调用收集器时,收集器会安装其信号处理程序,但它无法插入到信号处理例程。如果用户代码在连接后安装信号处理程序,则该处理程序会覆盖收集器的信号处理程序,且数据将会丢失。

请注意,包含任一分析信号的信号都可能会使系统调用过早终止。程序必须准备好处理此行为。当 libcollector 安装信号处理程序以进行数据收集时,它会指定重新启动可重新启动的系统调用。但是,类似 sleep(3C) 的某些系统调用会过早返回而不报告错误。

抽样信号和暂停-恢复信号

用户可以将信号指定为抽样信号 (-l) 或暂停-恢复信号 (-y)。建议将 SIGUSR1 或 SIGUSR2 用于此用途,但也可以使用目标未使用的任何信号。

如果进程未将分析信号用于其他用途,则可以使用这些信号,但仅在没有其他信号可用时才能使用这些信号。收集器会插入到某些信号处理例程且不允许用户为这些信号安装信号处理程序;它会保存用户的处理程序,就像收集器在启动实验时替换用户处理程序一样。

如果在连接到目标进程后调用收集器,且用户代码为抽样信号或暂停-恢复信号安装了一个信号处理程序,则这些信号将无法再按指定完成操作。

使用 setuidsetgid

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

collect 命令通过将共享库 libcollector.so 插入目标的地址空间 (LD_PRELOAD) 来运行。如果对调用 setuidsetgid 或创建调用 setuidsetgid 的子孙进程的可执行文件调用过的 collect 命令进行调用,可能会出现多个问题。如果您不是 root 用户,在运行实验时,收集会因为共享库未安装在可信目录中而失败。解决方法是以 root 用户身份运行实验,或使用 crle(1) 授予权限。应对安全障碍时请格外小心,操作风险需自行承担。

运行 collect 命令时,必须为您、由使用 exec() 执行的程序的 setuid 属性和 setgid 属性设置的任何用户或组以及该程序自身设置的任何用户或组,将 umask 设置为允许写权限。如果未正确设置掩码,某些文件可能无法写入实验,并且可能无法处理实验。如果可以写入日志文件,尝试处理实验时将显示错误。

如果目标本身发出了设置 UID 或 GID 的任何系统调用,或者如果目标更改其 umask,然后对其他某个可执行文件派生或运行 exec(),或者 crle 用于配置运行时链接程序如何搜索共享对象,则可能会出现其他问题。

如果在更改其有效 GID 的目标上以 root 用户身份启动实验,实验终止时自动运行的 er_archive 进程将失败,原因是它需要未标记为可信的共享库。在这种情况下,您可以在实验终止后立即在记录实验的计算机上运行 er_archive 实用程序(或 er_print 实用程序或 analyzer 命令)。

使用 libcollector 库从程序控制数据收集

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

API 函数定义如下所示。

void collector_sample(char *name);
void collector_pause(void);
void collector_resume(void);
void collector_terminate_expt(void);

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

C 和 C++ 接口

可以通过包括 collectorAPI.h 并与 -lcollectorAPI(包含用于检查底层 libcollector.so API 函数是否存在的实际函数)相链接来访问收集器 API 的 C 和 C++ 接口。

如果没有活动的实验,API 调用将被忽略。

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 是 Oracle Developer Studio 软件的安装目录。

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

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

CollectorAPI.sample(String name)
CollectorAPI.pause()
CollectorAPI.resume()
CollectorAPI.terminate()

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

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

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

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

如果要在多线程程序中使用 API 函数,应当确保它们只由一个线程调用。API 函数执行适用于进程(而不是单独的线程)的操作。如果每个线程都调用 API 函数,则记录的数据可能会与预期不同。例如,如果一个线程在其他线程到达程序中的同一点之前调用了 collector_pause()collector_terminate_expt(),则会针对所有线程暂停或终止收集,从而丢失那些正在执行 API 调用之前代码的线程的数据。

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

本节介绍 API 函数。

  • C 和 C++collector_sample(char *name)

    Fortran: collector_sample(string name)

    JavaCollectorAPI.sample(String name)

    记录抽样包并用指定的名称或字符串标记该抽样。 当在 "Timeline"(时间线)视图中选择一个抽样时,性能分析器将在 "Selection Details"(选择详细信息)窗口中显示此标签。Fortran 参数 string 的类型为 character

    抽样点包含进程(而不是单独的线程)的数据。在多线程应用程序中,如果在 collector_sample() API 函数记录抽样时发生另一个调用,则该函数可确保只写入一个抽样。所记录的抽样数可能会少于发出该调用的线程数。

    性能分析器不对由不同机制记录的抽样进行区分。如果只想查看 API 调用所记录的抽样,则应当在记录性能数据时关闭所有其他抽样模式。

  • C、C++ 和 Fortran:collector_pause()

    Java: CollectorAPI.pause()

    停止将特定于事件的数据写入实验。 实验将保持打开状态,并将继续写入全局数据。如果没有活动的实验或者已经停止记录数据,则该调用将被忽略。该函数停止写入所有特定于事件的数据,即使它是由 collector_thread_resume() 函数针对特定线程启用的也是如此。

  • C、C++ 和 Fortran:collector_resume()

    JavaCollectorAPI.resume()

    在调用 collector_pause() 之后恢复将特定于事件的数据写入实验。 如果没用活动的实验或数据记录功能处于活动状态,则该调用将被忽略。

  • C、C++ 和 Fortran:collector_terminate_expt()

    JavaCollectorAPI.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 HotSpot 虚拟机编译的 Java 方法,该虚拟机使用的是另一个接口。Java 接口提供已编译到收集器的方法的名称。您可以查看 Java 编译方法的函数数据和带注释的反汇编列表,但不能查看带注释的源代码列表。

本节介绍 API 函数。

collector_func_load() 函数

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

表 6  collector_func_load() 的参数列表
参数
定义
name
性能工具所使用的动态编译函数的名称。该名称不必是函数的实际名称。虽然该名称不应包含嵌入的空格或引号字符,但无须遵循通常的函数命名约定。
alias
用于描述函数的任意字符串。它可以是 NULL。它不经过任何方式的解释,可以包含嵌入的空格。它显示在分析器的 "Summary"(摘要)标签中。它可用于指示函数的内容或动态构造函数的原因。
sourcename
构造函数时所在源文件的路径。它可以是 NULL。该源文件用于带注释的源代码列表。
vaddr
函数的装入地址。
size
以字节为单位的函数大小。
lntsize
对行号表中条目数量的计数。如果未提供行号信息,则计数应为零。
lntable
包含 lntsize 条目的表,其中每个条目都是一对整数。第一个整数是偏移量,第二个整数是行号。在一个条目的偏移量和下一个条目中所给出的偏移量之间的所有指令都归属于在第一个条目中提供的行号。偏移量必须按数字升序列出,但行号的顺序可以是任意的。如果 lntableNULL,则没有可用的函数源代码列表,不过反汇编列表是可用的。

collector_func_unload() 函数

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