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 调试,有必要了解 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 将外联子例程与其他任何函数一样对待,唯一的不同是您不能使用其内部生成的名称在该函数中显式设置断点。
除了调试多线程程序这一普通功能外,dbx 还允许您在 OpenMP 程序中执行以下操作:
单步执行到并行区域中。因为并行区域是外联的并且从 OpenMP 运行时库中进行调用,所以单步执行实际涉及几个层的运行时库调用(这些调用由为此目的而创建的从属线程执行)。单步步入并行区域时,到达断点的第一个线程引起程序停止。此线程可能是从属线程而不是启动单步执行的主线程。
例如,请参阅编译器如何转换 OpenMP 代码中的 Fortran 代码,并假定主线程 t@1 位于第 10 行。当您单步执行到第 12 行时,将创建从属线程 t@2、t@3 和 t@4 来执行运行时库调用。线程 t@3 首先到达断点并导致程序停止执行。因此,由线程 t@1 启动的单步执行在线程 t@3 上结束。此行为不同于正常的单步执行(在正常的单步执行后,通常仍处在与此前相同的线程上)。
打印共享、专用和线程专用变量。dbx 可以打印所有共享、专用和线程专用变量。如果尝试打印并行区域之外的线程专用变量,则会打印主线程的副本。无法通过 whatis 命令判断一个变量是共享、专用还是线程专用变量。
当执行在并行区域中停止时,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 命令)列出所有线程,然后切换到各个线程以确定哪个线程是主线程。
当执行在并行区域中停止时,dump 命令可以打印专用变量的多个副本。在下例中,dump 命令打印变量 i 的两个副本:
[t@1 l@1]: dump i = 1 sum = 0.0 a = ARRAY i = 1000001 |
因为外联例程作为宿主例程的嵌套函数实现,而专用变量作为外联例程的局部变量实现,所以会打印变量 i 的两个副本。由于 dump 命令打印作用域内的所有变量,因此宿主例程中的 i 和外联例程中的 i 均会显示。
当在 OpenMP 程序中的并行区域内单步执行时,执行序列与源代码序列可能会不同。产生这种序列差异的原因在于并行区域中的代码通常会由编译器进行转换和重新排列。OpenMP 代码中的单步执行与优化代码中的单步执行类似,其中优化器经常会移动代码。