JavaScript is required to for searching.
跳过导航链接
退出打印视图
Oracle Solaris Studio 12.3:性能分析器     Oracle Solaris Studio 12.3 Information Library (简体中文)
search filter icon
search icon

文档信息

前言

1.  性能分析器概述

2.  性能数据

3.  收集性能数据

4.  性能分析器工具

5.  er_print 命令行性能分析工具

6.  了解性能分析器及其数据

数据收集的工作原理

实验格式

archives 目录

子实验

动态函数

Java 实验

记录实验

collect 实验

创建进程的 dbx 实验

正在运行的进程上的 dbx 实验

解释性能度量

基于时钟的分析

计时度量的准确性

计时度量的比较

硬件计数器溢出分析

数据空间分析和内存空间分析

同步等待跟踪

堆跟踪

MPI 跟踪

调用堆栈和程序执行

单线程执行和函数调用

共享对象之间的函数调用

信号

陷阱

尾部调用优化

显式多线程

基于 Java 技术的软件执行概述

Java 调用堆栈和机器调用堆栈

基于时钟的分析和硬件计数器溢出分析

Java 分析视图模式

Java 分析数据的用户视图模式

Java 分析数据的专家视图模式

Java 分析数据的计算机视图模式

OpenMP 软件执行概述

OpenMP 分析数据的用户视图模式

人工函数

用户模式调用堆栈

OpenMP 度量

OpenMP 分析数据的专家视图模式

OpenMP 分析数据的计算机视图模式

不完全的堆栈展开

中间文件

将地址映射到程序结构

进程映像

装入对象和函数

有别名的函数

非唯一函数名称

来自剥离共享库的静态函数

Fortran 备用入口点

克隆函数

内联函数

编译器生成的主体函数

外联函数

动态编译的函数

<Unknown> 函数

OpenMP 特殊函数

<JVM-System> 函数

<no Java callstack recorded> 函数

<Truncated-stack> 函数

<Total> 函数

与硬件计数器溢出分析相关的函数

将性能数据映射到索引对象

将数据地址映射到程序数据对象

数据对象描述符

<Total> 数据对象

<Scalars> 数据对象

<Unknown> 数据对象及其元素

将性能数据映射到内存对象

7.  了解带注释的源代码和反汇编数据

8.  操作实验

9.  内核分析

索引

调用堆栈和程序执行

调用堆栈是一系列程序计数器 (program counter, PC) 地址,表示来自程序内的指令。第一个 PC 称为 叶 PC,它位于堆栈的底部,是要执行的下一条指令的地址。下一个 PC 是对包含叶 PC 的函数的调用的地址;下一个 PC 是对该函数的调用的地址,依此类推,直至到达堆栈的顶部。每个这样的地址称为返回地址。记录调用堆栈的过程涉及从程序堆栈获取返回地址,这称为展开堆栈。有关展开失败的信息,请参见不完全的堆栈展开

调用堆栈中的叶 PC 用于将独占度量从性能数据分配到该 PC 所在的函数。堆栈中的每个 PC(包括叶 PC)用于将非独占度量分配到它所在的函数。

大多数时候,已记录调用堆栈中的 PC 自然地对应于出现在程序源代码中的函数,而且性能分析器的已报告度量直接对应于这些函数。但是,有时程序的实际执行并不与程序执行方式的简单直观模型相对应,而且性能分析器的已报告度量可能会引起混淆。有关此类情况的更多信息,请参见将地址映射到程序结构

单线程执行和函数调用

程序执行的最简单案例是单线程程序调用它自己的装入对象内的函数。

将程序装入到内存中开始执行时,会为其建立上下文,包括要执行的初始地址、初始寄存器集和堆栈(用于存储临时数据和用于跟踪函数如何相互调用的内存区域)。初始地址始终位于函数 _start()(它内置于每个可执行程序中)的开头。

程序运行时,将按顺序执行指令,直至遇到分支指令,该指令以及其他指令可能表示函数调用或条件语句。在分支点上,控制权转移到分支目标指定的地址,然后从该地址继续执行。(通常,已提交分支后的下一条指令以供执行:此指令称为分支延迟槽指令。但是,有些分支指令会取消分支延迟槽指令的执行。

当执行表示调用的指令序列时,返回地址被放入寄存器,且在被调用函数的第一条指令处继续执行。

在大多数情况下,在被调用函数的前几个指令中的某个位置,一个新(用于存储有关函数的信息的内存区域)会被推到堆栈上,而返回地址被放入该帧。然后,在被调用函数本身调用其他函数时可以使用用于返回地址的寄存器。函数即将返回时,将其帧从堆栈中弹出,而且控制权返回到从其调用该函数的地址。

共享对象之间的函数调用

一个共享对象中的函数调用另一个共享对象中的函数时,其执行情况比在程序内对函数的简单调用更复杂。每个共享对象都包含一个程序链接表 (Program Linkage Table, PLT),该表包含位于该共享对象外部并从该共享对象引用的每个函数的条目。最初,PLT 中每个外部函数的地址实际上是 ld.so(即动态链接程序)内的地址。第一次调用这样的函数时,控制权将转移到动态链接程序,该动态链接程序会解析对实际外部函数的调用并为后续调用修补 PLT 地址。

如果在执行三个 PLT 指令之一的过程中发生分析事件,则 PLT PC 会被删除,并将独占时间归属到调用指令。如果在通过 PLT 条目首次调用期间发生分析事件,但是叶 PC 不是 PLT 指令之一,PLT 和 ld.so 中的代码引起的任何 PC 都将归属到人工函数 @plt 中,该函数将累计非独占时间。每个共享对象都存在一个这样的人工函数。如果程序使用 LD_AUDIT 接口,则可能从不修补 PLT 条目,而且来自 @plt 的非叶 PC 可能发生得更频繁。

信号

将信号发送到进程时,会发生各种寄存器和堆栈操作,使得发送信号时的叶 PC 看起来好像是对系统函数 sigacthandler() 的调用的返回地址。sigacthandler() 调用用户指定的信号处理程序,就像任何函数调用另一个函数一样。

性能分析器将信号传送产生的帧视为普通帧。传送信号时的用户代码显示为调用系统函数 sigacthandler(),而 sigacthandler() 又显示为调用用户的信号处理程序。来自 sigacthandler() 和任何用户信号处理程序以及它们调用的任何其他函数的非独占度量,都显示为中断函数的非独占度量。

收集器通过插入 sigaction(),以确保其处理程序在收集时钟数据时是 SIGPROF 信号的主处理程序,而在收集硬件计数器溢出数据时是 SIGEMT 信号的主处理程序。

陷阱

陷阱可以由指令或硬件发出,而且由陷阱处理程序捕获。系统陷阱是指通过指令启动的陷阱,它们会陷入内核。所有系统调用均使用陷阱指令实现。一些硬件陷阱示例包括:当浮点单元无法完成指令或者当指令无法在硬件中实现时,浮点单元会发出硬件陷阱。

当发出陷阱时,内核将进入系统模式。在 Oracle Solaris 上,微状态通常从用户 CPU 状态切换到陷阱状态,再切换到系统状态。处理陷阱所用的时间可以显示为系统 CPU 时间和用户 CPU 时间的组合,具体取决于切换微状态的时间点。该时间被归属到用户代码中从其启动陷阱的指令(或归属到系统调用)。

对于某些系统调用,提供尽可能高效的调用处理被认为是很关键的。由这些调用生成的陷阱称为快速陷阱。生成快速陷阱的系统函数包括 gethrtimegethrvtime。在这些函数中,由于涉及到的开销,所以不会切换微状态

在其他情况下,提供尽可能高效的陷阱处理也被认为是很关键的。其中的一些示例是 TLB(translation lookaside buffer,转换后备缓冲器)未命中以及寄存器窗口溢出和填充,其中不切换微状态。

在这两种情况下,所用的时间都记录为用户 CPU 时间。但是,由于 CPU 模式已切换为系统模式,所以将关闭硬件计数器。因此,通过求出用户 CPU 时间和周期时间(最好在同一实验中记录)之间的差值,可以估算处理这些陷阱所用的时间。

有一种陷阱处理程序切换回用户模式的情况,那是 Fortran 中在 4 字节边界上对齐的 8 字节整数的未对齐内存引用陷阱。陷阱处理程序的帧出现在堆栈上,而对处理程序的调用可以出现在性能分析器中,归属到整数装入或存储指令。

指令陷入内核后,陷阱指令后的指令看起来要使用很长时间,这是因为它在内核完成陷阱指令的执行之前无法启动。

尾部调用优化

只要特定函数执行的最后一个操作是调用另一个函数,编译器就可以执行一种特定的优化。被调用方可重用来自调用方的帧,而不是生成新的帧,而且可从调用方复制被调用方的返回地址。此优化的动机是减小堆栈的大小,以及(在 SPARC 平台上)减少对寄存器窗口的使用。

假定程序源代码中的调用序列与如下所示类似:

A -> B -> C -> D

BC 进行尾部调用优化后,调用堆栈看起来好像是函数 A 直接调用函数 BCD

A -> B
A -> C
A -> D

也就是说,调用树被展平。使用 -g 选项编译代码时,尾部调用优化仅发生在编译器优化级别 4 或更高级别上。在不使用 -g 选项的情况下编译代码时,尾部调用优化发生在编译器优化级别 2 或更高级别上。

显式多线程

简单的程序在单线程中执行。多线程可执行程序调用线程创建函数(执行的目标函数会传递到该函数)。目标退出时,会销毁线程。

Oracle Solaris 支持两种线程实现:Solaris 线程和 POSIX 线程 (Pthread)。从 Oracle Solaris 10 开始,这两种线程实现都包括在 libc.so 中。

对于 Solaris 线程,新创建的线程从名为 _thread_start() 的函数开始执行,该函数调用在线程创建调用中传递的函数。对于涉及此线程执行的目标的任何调用堆栈,堆栈的顶部是 _thread_start(),与线程创建函数的调用方没有任何联系。因此,与所创建的线程关联的非独占度量最多仅向上传播至 _thread_start()<Total> 函数。除了创建线程外,Solaris 线程实现还在 Solaris 上创建 LWP 以执行线程。每个线程都绑定到特定的 LWP。

Oracle Solaris 和 Linux 中都提供了用于显式多线程的 Pthread。

在这两种环境中,为创建新线程,应用程序会调用 Pthread API 函数 pthread_create(),将指针作为函数参数之一传递到应用程序定义的启动例程。

在早于 Oracle Solaris 10 的 Solaris 版本上,新的 pthread 开始执行时,将会调用 _lwp_start() 函数。从 Oracle Solaris 10 开始,_lwp_start() 调用中间函数 _thrp_setup(),该中间函数随后调用在 pthread_create() 中指定的应用程序定义的启动例程。

在 Linux 操作系统上,新的 pthread 开始执行时,将会运行 Linux 特定的系统函数 clone(),该系统函数调用另一个内部初始化函数 pthread_start_thread(),该初始化函数又调用在 pthread_create() 中指定的应用程序定义的启动例程。可用于收集器的 Linux 度量收集函数是线程特定的。因此,collect 实用程序运行时,会在 pthread_start_thread() 和应用程序定义的线程启动例程之间插入一个名为 collector_root() 的度量收集函数。

基于 Java 技术的软件执行概述

对于典型的开发者,基于 Java 技术的应用程序就像任何其他程序那样运行。此类应用程序从主入口点(通常名为 class.main,可以调用其他方法)开始,就像 C 或 C++ 应用程序那样。

对于操作系统,使用 Java 编程语言(纯 Java 或与 C/C++ 混合)编写的应用程序作为实例化 JVM 软件的进程运行。JVM 软件是从 C++ 源代码编译的,从 _start(它会调用 main 等)开始执行。它从 .class 和/或 .jar 文件读取字节码,并执行在该程序中指定的操作。可以指定的操作包括动态装入本机共享对象以及调用该对象内包含的各种函数或方法。

JVM 软件可以执行许多用传统语言编写的应用程序通常不能执行的操作。启动时,该软件会在其数据空间中创建许多动态生成的代码的区域。其中一个区域是用于处理应用程序的字节码方法的实际解释器代码。

在执行基于 Java 技术的应用程序期间,JVM 软件解释大多数方法;这些方法称为已解释的方法。Java HotSpot 虚拟机会在解释字节码以检测频繁执行的方法时监视性能。然后,Java HotSpot 虚拟机可能编译重复执行的方法,以生成这些方法的机器码。生成的方法称为已编译的方法。之后,虚拟机执行更高效的已编译方法,而不是解释方法的原始字节码。已编译的方法会被装入应用程序的数据空间,并且可能会在之后的某个时间点卸载它们。此外,还会在数据空间中生成其他代码以执行已解释代码和已编译代码之间的转换。

用 Java 编程语言编写的代码还可以直接调用本机编译的代码(C、C++ 或 Fortran);此类调用的目标称为本机方法。

用 Java 编程语言编写的应用程序本身就是多线程的,对于用户程序中的每个线程,都具有一个 JVM 软件线程。Java 应用程序还具有若干个内务处理线程,用于信号处理、内存管理和 Java HotSpot 虚拟机编译。

在 J2SE 中的 JVMTI 上,可通过各种方法实现数据收集。

Java 调用堆栈和机器调用堆栈

性能工具通过记录每个线程生存期中的事件,以及发生事件时的调用堆栈来收集其数据。在执行任何应用程序的任意点上,调用堆栈表示程序在其执行中所处的位置以及它如何到达该位置。混合模型 Java 应用程序区别于传统 C、C++ 和 Fortran 应用程序的一个重要方面是,在运行目标的过程中的任何瞬间都存在两个有意义的调用堆栈:Java 调用堆栈和机器调用堆栈。这两个调用堆栈都在配置期间进行记录,并在分析期间进行协调。

基于时钟的分析和硬件计数器溢出分析

用于 Java 程序的基于时钟的分析和硬件计数器溢出分析的工作方式与用于 C、C++ 和 Fortran 程序的情况基本相同,不同之处在于前者会同时收集 Java 调用堆栈和机器调用堆栈。

Java 分析视图模式

性能分析器为用 Java 编程语言编写的应用程序提供了三种显示性能数据的视图模式:用户模式、专家模式和计算机模式。缺省情况下,将显示用户模式(前提是数据支持它)。下一节汇总了这三种视图模式的主要差异。

Java 分析数据的用户视图模式

用户模式按名称显示已编译的和已解释的 Java 方法,并以其自然形式显示本机方法。在执行过程中,可能存在已执行的特定 Java 方法的许多实例:已解释的版本,也许还有一个或多个已编译的版本。在用户模式中,所有方法会被聚集显示为一个方法。缺省情况下,在分析器中选定此视图模式。

用户视图模式中 Java 方法的 PC 与该方法中的方法 id 和字节码索引相对应;本机函数的 PC 与机器 PC 相对应。Java 线程的调用堆栈可能同时具有 Java PC 和机器 PC。它没有对应于 Java 内务处理代码(无 Java 表示法)的任何帧。在某些情况下,JVM 软件无法展开 Java 堆栈,将返回单个帧及特殊函数 <no Java callstack recorded>。通常,它占总时间的比例不会超过 5-10%。

用户模式中的函数列表针对所调用的 Java 方法和任何本机方法显示度量。调用方-被调用方标签显示用户模式中的调用关系。

Java 方法的源代码对应于 .java 文件(从中编译源代码,每个源代码行上都有度量)中的源代码。任何 Java 方法的反汇编显示为其生成的字节码,以及针对每个字节码的度量和交错的 Java 源代码(如果可用)。

Java 表示法中的时间线仅显示 Java 线程。每个线程的调用堆栈与其 Java 方法一起显示。

当前不支持 Java 表示法中的数据空间分析。

Java 分析数据的专家视图模式

专家模式类似于用户模式,只不过在用户模式下禁止的一些 JVM 内部详细信息会在专家模式中公开。对于专家模式,时间线显示所有线程;内务处理线程的调用堆栈是本机调用堆栈。

Java 分析数据的计算机视图模式

计算机模式显示来自 JVM 软件本身而不是来自 JVM 软件解释的应用程序的函数。该表示法还显示所有已编译方法和本机方法。计算机模式看起来与用传统语言编写的应用程序的计算机模式相同。调用堆栈显示 JVM 帧、本地帧和编译方法帧。一些 JVM 帧表示已解释的 Java、已编译的 Java 和本机代码之间的转换代码。

针对 Java 源代码显示已编译方法的源代码;数据表示所选已编译方法的特定实例。已编译方法的反汇编显示生成的机器汇编程序代码,而不是 Java 字节码。调用方-被调用方关系显示所有开销帧,以及表示已解释方法、已编译方法和本机方法之间的转换的所有帧。

计算机视图模式中的时间线以条形图显示所有线程、LWP 或 CPU,而其中每项的调用堆栈都是调用堆栈的计算机模式。

OpenMP 软件执行概述

OpenMP 应用程序的实际执行模型在 OpenMP 规范中进行了描述(例如,请参见《OpenMP Application Program Interface, Version 3.0》中的 1.3 节)。但是,该规范未描述对用户可能很重要的一些实现详细信息,Oracle 的实际实现是这样的:通过直接记录的分析信息,用户并不能轻松了解线程是如何交互的。

在任何单线程程序运行时,其调用堆栈会显示其当前位置,以及如何到达那里的跟踪,跟踪从名为 _start 的例程(该例程调用 main,后者又继续调用程序内的各种子例程)中的起始指令开始。如果子例程包含循环,则程序会重复执行循环内的代码,直至达到循环退出条件。然后继续执行下一个代码序列,依此类推。

通过 OpenMP(或通过自动并行化)并行化程序时,行为是不同的。该并行化程序的直观模型具有像单线程程序那样执行的主线程。当它到达并行循环或并行区域时,将出现其他从属线程(每个都是主线程的克隆),它们都并行执行循环或并行区域的内容,每个从属线程执行不同的工作块。在完成所有工作块时,所有线程都是同步的,从属线程将消失,而主线程继续运行。

并行化程序的实际行为并非如此简单。在 Oracle 实现中,当编译器为并行区域或循环(或任何其他 OpenMP 构造)生成代码时,将提取其内部的代码,并使之成为一个名为 mfunction 的独立函数。(也可以将它称为外联函数或循环体函数。)mfunction 的名称是对 OpenMP 构造类型、从中提取它的函数的名称以及该构造所在源代码行的行号进行编码的结果。这些函数的名称在分析器的专家模式和计算机模式下按以下形式显示,其中方括号中的名称是函数的实际符号表名称:

bardo_ -- OMP parallel region from line 9 [_$p1C9.bardo_]
atomsum_ -- MP doall from line 7 [_$d1A7.atomsum_]

还有其他形式的此类函数,它们是从其他源代码构造派生的,名称中的 OMP parallel region 会被替换为 MP constructMP doallOMP sections。在下面的讨论中,所有这些都统称为并行区域

执行并行循环内代码的每个线程都可以多次调用其 mfunction,每次调用执行循环内的一个工作块。在所有工作块完成时,每个线程调用库中的同步或归约例程;然后主线程继续,而从属线程变为空闲状态,等待主线程进入下一个并行区域。所有调度和同步都是通过对 OpenMP 运行时的调用处理的。

在其执行过程中,并行区域内的代码可能执行一个工作块,或者它可能与其他线程同步,或者选择要执行的其他工作块。它还可能调用其他函数,而这些函数又可能会再调用其他函数。在并行区域内执行的从属线程(或主线程)可能本身(或者通过它调用的函数)充当主线程,并进入它自己的并行区域,从而导致嵌套并行操作。

分析器基于调用堆栈的统计抽样收集数据,跨所有线程聚集其数据,并针对函数、调用方和被调用方、源代码行和指令,基于所收集数据的类型显示性能度量。分析器按以下三种视图模式之一提供有关 OpenMP 程序性能的信息:用户模式、专家模式和计算机模式。

有关 OpenMP 程序的数据收集的更多详细信息,请参见 OpenMP 用户社区 Web 站点上的《An OpenMP Runtime API for Profiling》

OpenMP 分析数据的用户视图模式

分析数据的用户模式显示尝试提供信息,好像程序按照OpenMP 软件执行概述中所述的直观模型实际执行一样。在计算机模式下显示的实际数据捕获运行时库 libmtsk.so(它不对应于模型)的实现详细信息。专家模式显示为匹配模型而改变的混合数据和实际数据。

在用户模式下,更改了分析数据的显示以便更好地匹配模型,在以下三个方面不同于记录的数据和机器模式显示:

人工函数

构造人工函数,并将其放置在用户模式和专家模式调用堆栈上,以反映线程在 OpenMP 运行时库中处于某个状态的事件。

定义了以下人工函数:

<OMP-overhead>
在 OpenMP 库中执行
<OMP-idle>
从属线程,等待工作
<OMP-reduction>
执行归约操作的线程
<OMP-implicit_barrier>
在隐式屏障处等待的线程
<OMP-explicit_barrier>
在显式屏障处等待的线程
<OMP-lock_wait>
等待锁定的线程
<OMP-critical_section_wait>
等待进入临界段的线程
<OMP-ordered_section_wait>
等待轮流进入排序段的线程
<OMP-atomic_section_wait>
等待 OpenMP 原子构造的线程。

当线程处于对应于其中一个人工函数的 OpenMP 运行时状态时,会将该人工函数作为堆栈上的叶函数添加。当线程的实际叶函数处于 OpenMP 运行时中的任意位置时,<OMP-overhead> 将作为叶函数替换它。否则,从用户模式堆栈中忽略 OpenMP 运行时中的所有 PC。

对于 OpenMP 3.0 程序,不使用 <OMP-overhead> 人工函数。由“OpenMP 开销”度量替换人工函数。

用户模式调用堆栈

对于 OpenMP 实验,用户模式显示重构的调用堆栈,这些重构的调用堆栈类似于在不使用 OpenMP 的情况下编译程序时获取的调用堆栈。目的在于以与程序的直观了解相匹配的方式提供分析数据,而不是显示实际处理的所有详细信息。当 OpenMP 运行时库执行特定操作时,将协调主线程与从线程的调用堆栈并将人工 <<OMP-*> 函数添加到此调用堆栈。

OpenMP 度量

处理 OpenMP 程序的时钟分析事件时,将显示两个度量(它们分别对应于 OpenMP 系统中的两种状态所用的时间):“OpenMP 工作”和“OpenMP 等待”。

只要从用户代码执行线程(不管串行执行还是并行执行),就会在“OpenMP 工作”中累计时间。只要线程正在等待某项,之后才能继续,就会在“OpenMP 等待”中累计时间,而不管等待是忙等待(自旋等待)还是休眠。这两个度量的总和与时钟分析中的“总线程”度量相匹配。

在用户模式、专家模式和计算机模式下显示“OpenMP 等待”和“OpenMP 工作”度量。

OpenMP 分析数据的专家视图模式

查看专家视图模式下的 OpenMP 实验时,如果 OpenMP 运行时正在执行某些特定操作,您将看到格式为 <OMP-*> 的人工函数,这一点类似于用户视图模式。但是,专家视图模式单独显示表示并行化循环、任务等的编译器生成的 mfunction。用户模式会将这些编译器生成的 mfunction 与用户函数聚集在一起。

OpenMP 分析数据的计算机视图模式

机器模式显示所有线程和编译器生成的外联函数的本机调用堆栈。

在执行的各个阶段中,程序的实际调用堆栈与上面在直观模型中描述的有很大差异。计算机模式将调用堆栈显示为已度量,没有进行转换,且没有构造人工函数。但是,仍显示时钟分析度量。

在下面的每个调用堆栈中,libmtsk 表示 OpenMP 运行时库内调用堆栈中的一个或多个帧。就像屏障代码的内部实现或执行归约一样,出现哪些函数以及出现顺序的详细信息随 OpenMP 的发行版的不同而不同。

  1. 在第一个并行区域之前

    在进入第一个并行区域之前,只有一个线程,即主线程。调用堆栈与用户模式和专家模式下的完全相同。

    主线程
    foo
    main
    _start
  2. 在并行区域中执行时

    主线程
    从属线程 1
    从属线程 2
    从属线程 3
    foo-OMP...
    libmtsk
    foo
    foo-OMP...
    foo-OMP...
    foo-OMP...
    main
    libmtsk
    libmtsk
    libmtsk
    _start
    _lwp_start
    _lwp_start
    _lwp_start

    在机器模式下,从属线程显示为在 _lwp_start 中启动,而不是在 _start(主线程在其中启动)中启动。(在线程库的某些版本中,该函数可能显示为 _thread_start。对 foo-OMP... 的调用表示为并行化区域生成的 mfunction。

  3. 所有线程都在屏障处时

    主线程
    从属线程 1
    从属线程 2
    从属线程 3
    libmtsk
    foo-OMP...
    foo
    libmtsk
    libmtsk
    libmtsk
    main
    foo-OMP...
    foo-OMP...
    foo-OMP...
    _start
    _lwp_start
    _lwp_start
    _lwp_start

    与线程在并行区域中执行时不同,当线程在屏障处等待时,在 foo 和并行区域代码 foo-OMP... 之间没有来自 OpenMP 运行时的帧。原因是实际执行中不包括 OMP 并行区域函数,但 OpenMP 运行时处理寄存器,以便堆栈展开显示从最后执行的并行区域函数到运行时屏障代码的调用。如果没有它,在机器模式下将无法确定哪个并行区域与屏障调用相关。

  4. 离开并行区域之后

    主线程
    从属线程 1
    从属线程 2
    从属线程 3
    foo
    main
    libmtsk
    libmtsk
    libmtsk
    _start
    _lwp_start
    _lwp_start
    _lwp_start

    在从属线程中,没有用户帧位于调用堆栈上。

  5. 在嵌套并行区域中时

    主线程
    从属线程 1
    从属线程 2
    从属线程 3
    从属线程 4
    bar-OMP...
    foo-OMP...
    libmtsk
    libmtsk
    bar
    foo
    foo-OMP...
    foo-OMP...
    foo-OMP...
    bar-OMP...
    main
    libmtsk
    libmtsk
    libmtsk
    libmtsk
    _start
    _lwp_start
    _lwp_start
    _lwp_start
    _lwp_start

不完全的堆栈展开

堆栈展开在调用堆栈和程序执行中定义。

堆栈展开可能由于多种原因而失败:

中间文件

如果使用 -E-P 编译器选项生成中间文件,则分析器将中间文件用于带注释的源代码,而不是原始源文件。使用 -E 生成的 #line 指令可能会导致为源代码行分配度量时出现问题。

如果函数中的指令没有行号(这些行号引用为生成该函数而编译的源文件),则带注释的源代码中会出现以下行:

function_name -- <instructions without line numbers>

在以下情况下可能缺少行号: