Sun Studio 12:使用 dbx 调试程序

第 13 章 调试 OpenMP 程序

OpenMPTM 应用编程接口 (application programming interface,API) 是 Sun 与多家计算机供应商为共享内存多处理器体系结构合作联合开发的可移植并行程序设计模型。对使用 dbx 调试 Fortran、C++ 和 C OpenMP 程序的支持基于 dbx 的通用多线程调试功能。对线程和 LWP 进行操作的所有 dbx 命令都可用于 OpenMP 调试。dbx 不支持 OpenMP 调试中的异步线程控制。

本章由以下部分组成:

有关组成 OpenMP 2.0 版应用程序接口(通过 Sun Studio Fortran 95 和 C 编译器实现)的指令、运行时库例程和环境变量的信息,请参见《OpenMP API 用户指南》。


注 –

OpenMP 调试只在运行 Solaris OS 的平台上可用。该功能在 Linux 平台上不可用。


编译器如何转换 OpenMP 代码

要更好地介绍 OpenMP 调试,有必要了解 OpenMP 代码是如何由编辑器进行转换的。参见下列 Fortran 示例:


1    program example
2        integer i, n
3        parameter (n = 1000000)
4        real sum, a(n)
5    
6        do i = 1, n
7        a(i) = i*i
8        end do
9    
10        sum = 0
11    
12    !$OMP PARALLEL DO DEFAULT(PRIVATE), SHARED(a, sum)
13    
14        do i = 1, n
15        sum = sum + a(i)
16        end do
17    
18    !$OMP END PARALLEL DO
19    
20        print*, sum
21       end program example

第 12 至 18 行的代码是一个并行区域。f95 编译器将这部分代码转换成从 OpenMP 运行时库中调用的外联子例程。此外联子例程有一个内部生成的名称,在本例中为 _ _$d1A12.MAIN_。然后,f95 编译器用一个 OpenMP 运行时库调用替换并行区域的代码,并将外联子例程作为其中一个参数进行传递。该 OpenMP 运行时库将处理线程相关的所有问题,并分配并行执行外联子例程的从属线程。C 编译器的工作原理与此相同。

调试 OpenMP 程序时,dbx 将外联子例程与其他任何函数一样对待,唯一的不同是您不能使用其内部生成的名称在该函数中显式设置断点。

可用于 OpenMP 代码的 dbx 功能

除了调试多线程程序这一普通功能外,dbx 还允许您在 OpenMP 程序中执行以下操作:

使用带 OpenMP 代码的栈跟踪

当执行在并行区域中停止时,where 命令会显示既包含数个运行时库调用又包含外联子例程的栈跟踪。使用编译器如何转换 OpenMP 代码中的 Fortran 示例,并且在第 15 行停止执行时,where 命令将生成以下栈跟踪。


[t@4 l@4]: where
current thread: t@4
=>[1] _$d1A12.MAIN_(), line 15 in "example.f90"
[2] __mt_run_my_job_(0x45720, 0xff82ee48, 0x0, 0xff82ee58, 0x0, 0x0), at 0x16860
[3] __mt_SlaveFunction_(0x45720, 0x0, 0xff82ee48, 0x0, 0x455e0, 0x1), at 0x1aaf0

栈的顶帧是外联函数帧。尽管代码是外联的,源代码行号仍映射回 15。其他两帧用于运行时库调用。

当执行在并行区域中停止时,来自从属线程的 where 命令并未使栈回溯至其父线程(如上例所示)。但是,主线程中的 where 命令却具有完全回溯:


[t@4 l@4]: thread t@1
t@1 (l@1) stopped in _$d1A12.MAIN_ at line 15 in file "example.f90"
15           sum = sum + a(i)
[t@1 l@1]: where
current thread: t@1
=>[1] _$d1A12.MAIN_(), line 15 in "example.f90"
[2] __mt_run_my_job_(0x41568, 0xff82ee48, 0x0, 0xff82ee58, 0x0, 0x0), at 0x16860
[3] __mt_MasterFunction_(0x1, 0x0, 0x6, 0x0, 0x0, 0x40d78), at 0x16150
[4] MAIN(), line 12 in "example.f90"

如果线程数不大,则可以通过以下方法确定执行如何到达从属线程中的断点:使用 threads 命令(请参见 threads 命令)列出所有线程,然后切换到各个线程以确定哪个线程是主线程。

在 OpenMP 代码上使用 dump 命令

当执行在并行区域中停止时,dump 命令可以打印专用变量的多个副本。在下例中,dump 命令打印变量 i 的两个副本:


[t@1 l@1]: dump
i = 1
sum = 0.0
a = ARRAY
i = 1000001

因为外联例程作为宿主例程的嵌套函数实现,而专用变量作为外联例程的局部变量实现,所以会打印变量 i 的两个副本。由于 dump 命令打印作用域内的所有变量,因此宿主例程中的 i 和外联例程中的 i 均会显示。

OpenMP 代码的执行序列

当在 OpenMP 程序中的并行区域内单步执行时,执行序列与源代码序列可能会不同。产生这种序列差异的原因在于并行区域中的代码通常会由编译器进行转换和重新排列。OpenMP 代码中的单步执行与优化代码中的单步执行类似,其中优化器经常会移动代码。