分析数据的用户模式显示尝试提供信息,好像程序按照OpenMP 软件执行概述中所述的模型实际执行一样。实际的数据捕获运行时库 libmtsk.so(它不对应于模型)的实现详细信息。在用户模式下,更改了分析数据的显示以便更好地匹配模型,在以下三个方面不同于记录的数据和机器模式显示:
从 OpenMP 运行时库的角度来看,构造的人工函数表示每个线程的状态。
处理调用栈以报告对应于代码运行方式模型的数据,如上所述。
为基于时钟的分析实验构造另外两个性能度量,它们分别对应于执行有用工作所用的时间和在 OpenMP 运行时中等待所用的时间。
构造人工函数,并将其放置在用户模式调用栈上,以反映线程在 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>-等待轮流进入排序段的线程
当线程处于对应于其中一个函数的 OpenMP 运行时状态时,会将对应函数作为堆栈上的叶函数添加。当线程的叶函数处于 OpenMP 运行时中的任意位置时,<OMP-overhead> 将作为叶函数替换它。否则,从用户模式堆栈中忽略 OpenMP 运行时中的所有 PC。
了解此模型的最简单方法是查看 OpenMP 程序在其执行过程中各个时刻的调用栈。本节介绍一个简单的程序,该程序具有调用一个子例程 foo 的主程序。该子例程具有单个并行循环,线程在其中完成工作、争用、获取和释放锁,以及进入和离开临界段。显示另一组调用栈,反映一个从属线程调用了另一函数 bar(它进入嵌套并行区域)时的状态。
在此显示中,并行区域中所用的所有包含时间都包括在从中提取它的函数的包含时间中,其中包括在 OpenMP 运行时中所用的时间,而且该包含时间一直向上传播到 main 和 _start。
表示此模型中行为的调用栈如后续小节中所示。并行区域函数的实际名称具有以下形式,如上所述:
foo -- OMP parallel region from line 9[ [_$p1C9.foo] bar -- OMP parallel region from line 5[ [_$p1C5.bar]
为了清晰起见,在说明中使用以下简化形式:
foo -- OMP... bar -- OMP...
在说明中,所有线程的调用栈都在执行程序期间的某个时刻显示。每个线程的调用栈均显示为帧堆栈,将在分析器时间线标签中为单个线程选择单独分析事件得到的数据与顶部的叶 PC 匹配。在时间线标签中,每个帧都显示有 PC 偏移(在下面省略了此偏移)。所有线程的堆栈都在水平数组中显示,而在分析器时间线标签中,其他线程的堆栈将出现在垂直堆叠的分析数据栏中。此外,在提供的表示法中,将显示所有线程的堆栈,就像它们是在同一时刻捕获的那样,而在实际实验中,堆栈在每个线程中独立地捕获,并且彼此之间可能存在相对偏离。
所示的调用栈表示的数据如在分析器中或在 er_print 实用程序中通过用户视图模式显示的一样。
在第一个并行区域之前
在进入第一个并行区域之前,只有一个线程,即主线程。
主线程 |
---|
foo |
main |
_start |
进入第一个并行区域时
此时,库已创建从属线程,而且所有线程(主线程和从属线程)即将开始处理其工作块。所有线程都显示为从构造的 OpenMP 指令所在行上的 foo,或者从包含已自动并行化的循环语句的行,调用到并行区域 foo-OMP... 的代码中。每个线程中并行区域的代码从并行区域中的第一个指令调用到 OpenMP 支持库(显示为 <OMP-overhead> 函数)中。
主线程 |
从属线程 1 |
从属线程 2 |
从属线程 3 |
---|---|---|---|
<OMP-overhead> |
<OMP-overhead> |
<OMP-overhead> |
<OMP-overhead> |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo |
foo |
foo |
foo |
main |
main |
main |
main |
_start |
_start |
_start |
_start |
其中可能出现 <OMP-overhead> 的窗口相当小,所以该函数可能不出现在任何特定实验中。
在并行区域中执行时
所有四个线程都在并行区域中执行有用的工作。
主线程 |
从属线程 1 |
从属线程 2 |
从属线程 3 |
---|---|---|---|
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo |
foo |
foo |
foo |
main |
main |
main |
main |
_start |
_start |
_start |
_start |
在并行区域中工作块之间执行时
所有四个线程都在执行有用的工作,但是一个线程已完成一个工作块,并正在获取其下一个工作块。
主线程 |
从属线程 1 |
从属线程 2 |
从属线程 3 |
---|---|---|---|
<OMP-overhead> | |||
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo |
foo |
foo |
foo |
main |
main |
main |
main |
_start |
_start |
_start |
_start |
在并行区域内的临界段中执行时
所有四个线程都在执行,每个线程都在并行区域内。其中一个线程在临界段中,而其他线程之一在到达临界段之前(或完成它之后)正在运行。剩余的两个线程正在等待,以便它们自己进入临界段。
主线程 |
从属线程 1 |
从属线程 2 |
从属线程 3 |
---|---|---|---|
<OMP-critical_section_wait> |
<OMP-critical_section_wait> |
||
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo |
foo |
foo |
foo |
main |
main |
main |
main |
_start |
_start |
_start |
_start |
收集的数据不区分正在临界段中执行的线程的调用栈与尚未到达或已通过临界段的线程的调用栈。
在并行区域内的锁定周围执行时
锁定周围的代码段完全类似于临界段。所有四个线程都在并行区域内执行。一个线程在持有锁定时执行,一个在获取锁定之前(或获取并释放它之后)执行,另外两个线程正在等待锁定。
主线程 |
从属线程 1 |
从属线程 2 |
从属线程 3 |
---|---|---|---|
<OMP-lock_wait> |
<OMP-lock_wait> |
||
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo |
foo |
foo |
foo |
main |
main |
main |
main |
_start |
_start |
_start |
_start |
与临界段示例中一样,收集的数据不区分持有锁定并执行的线程的调用栈与在获取锁定之前或释放锁定之后执行的线程的调用栈。
在并行区域结尾附近
此时,其中三个线程已完成其所有工作块,但是还有一个线程仍在工作中。在这种情况下,OpenMP 构造隐式指定了屏障;如果用户代码已显式指定屏障,则 <OMP-implicit_barrier> 函数将由 <OMP-explicit_barrier> 替换。
主线程 |
从属线程 1 |
从属线程 2 |
从属线程 3 |
---|---|---|---|
<OMP-implicit_barrier> |
<OMP-implicit_barrier> |
|
<OMP-implicit_barrier> |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo |
foo |
foo |
foo |
main |
main |
main |
main |
_start |
_start |
_start |
_start |
在并行区域结尾附近,具有一个或多个归约变量
此时,其中两个线程已完成其所有工作块,而且正在执行归约计算,但是其中一个线程仍在工作,第四个线程已完成其归约部分,正在屏障处等待。
主线程 |
从属线程 1 |
从属线程 2 |
从属线程 3 |
---|---|---|---|
<OMP-reduction> |
<OMP-implicit_barrier> |
|
<OMP-implicit_barrier> |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo |
foo |
foo |
foo |
main |
main |
main |
main |
_start |
_start |
_start |
_start |
尽管在 <OMP-reduction> 函数中显示了一个线程,但是执行归约所用的实际时间通常相当少,且在调用栈样本中很少捕获到。
在并行区域的结尾
此时,所有线程均已完成并行区域内的所有工作块,且已到达屏障。
主线程 |
从属线程 1 |
从属线程 2 |
从属线程 3 |
---|---|---|---|
<OMP-implicit_barrier> |
<OMP-implicit_barrier> |
<OMP-implicit_barrier> |
<OMP-implicit_barrier> |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo |
foo |
foo |
foo |
main |
main |
main |
main |
_start |
_start |
_start |
_start |
由于所有线程均已到达屏障,因此它们都可能会继续,实验不大可能找到处于此状态的所有线程。
离开并行区域之后
此时,所有从属线程都在等待进入下一个并行区域,它们处于自旋或休眠状态,具体状态取决于用户设置的各种环境变量。程序以串行方式执行。
主线程 |
从属线程 1 |
从属线程 2 |
从属线程 3 |
---|---|---|---|
foo | |||
main | |||
_start |
<OMP-idle> |
<OMP-idle> |
<OMP-idle> |
在嵌套并行区域中执行时
所有四个线程都在工作,每个线程都在外部并行区域内。其中一个从属线程已调用另一函数 bar,并已创建嵌套并行区域,而且会创建一个附加从属线程以便与它一起工作。
主线程 |
从属线程 1 |
从属线程 2 |
从属线程 3 |
从属线程 4 |
---|---|---|---|---|
bar-OMP... |
bar-OMP... |
|||
bar |
bar |
|||
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo-OMP... |
foo |
foo |
foo |
foo |
foo |
main |
main |
main |
main |
main |
_start |
_start |
_start |
_start |
_start |