我知道 OpenMP 实际上只是一组编译成 pthread 的宏。有没有办法在编译的其余部分发生之前查看 pthread 代码?我正在使用 GCC 进行编译。
3 回答
首先,OpenMP不是一组简单的宏。可以看到将其简单地转换为类似 pthread 的代码,但 OpenMP 确实需要的不仅仅是运行时支持。
回到您的问题,至少在 GCC 中,您看不到 pthread 代码,因为 GCC 的 OpenMP 实现是在编译器后端(或中间端)中完成的。转换是在 IR(中间表示)级别完成的。因此,从程序员的角度来看,代码实际上是如何转换的并不容易。
不过,也有一些参考。
(1) 一位英特尔工程师对在英特尔 C/C++ 编译器中实现 OpenMP 进行了很好的概述:
http://www.drdobbs.com/parallel/how-do-openmp-compilers-work-part-1/226300148
http://www.drdobbs.com/parallel/how-do-openmp-compilers-work-part-2/226300277
(2)大家可以看一下GCC的OpenMP的实现:
https://github.com/mirrors/gcc/tree/master/libgomp
Seelibgomp.h
确实使用 pthread,并loop.c
包含并行循环构造的实现。
OpenMP 是一组编译器指令,而不是宏。在 C/C++ 中,这些指令是通过#pragma
扩展机制实现的,而在 Fortran 中,它们是作为特殊格式的注释实现的。这些指令指示编译器执行某些代码转换,以便将串行代码转换为并行代码。
尽管可以将 OpenMP 实现为纯 pthreads 代码的转换,但很少这样做。OpenMP 机制的大部分通常内置在单独的运行时库中,该库作为编译器套件的一部分提供。对于 GCC,这是libgomp
. 它提供了一组用于轻松实现 OpenMP 结构的高级函数。它也是编译器内部的,不打算供用户代码使用,即没有提供头文件。
使用 GCC,可以获得 OpenMP 转换后代码外观的伪代码表示。您必须为其提供-fdump-tree-all
选项,这将导致编译器为每个编译单元生成大量中间文件。最有趣的是filename.017t.ompexp
(来自 GCC 4.7.1,其他 GCC 版本的数字可能不同,但扩展名仍然是.ompexp
)。该文件包含 OpenMP 结构被降低然后扩展为正确实现之后的代码中间表示。
考虑以下示例 C 代码,另存为fun.c
:
void fun(double *data, int n)
{
#pragma omp parallel for
for (int i = 0; i < n; i++)
data[i] += data[i]*data[i];
}
的内容fun.c.017t.ompexp
是:
fun (double * data, int n)
{
...
struct .omp_data_s.0 .omp_data_o.1;
...
<bb 2>:
.omp_data_o.1.data = data;
.omp_data_o.1.n = n;
__builtin_GOMP_parallel_start (fun._omp_fn.0, &.omp_data_o.1, 0);
fun._omp_fn.0 (&.omp_data_o.1);
__builtin_GOMP_parallel_end ();
data = .omp_data_o.1.data;
n = .omp_data_o.1.n;
return;
}
fun._omp_fn.0 (struct .omp_data_s.0 * .omp_data_i)
{
int n [value-expr: .omp_data_i->n];
double * data [value-expr: .omp_data_i->data];
...
<bb 3>:
i = 0;
D.1637 = .omp_data_i->n;
D.1638 = __builtin_omp_get_num_threads ();
D.1639 = __builtin_omp_get_thread_num ();
...
<bb 4>:
... this is the body of the loop ...
i = i + 1;
if (i < D.1644)
goto <bb 4>;
else
goto <bb 5>;
<bb 5>:
<bb 6>:
return;
...
}
为简洁起见,我省略了大部分输出。这不完全是 C 代码。它是程序流的类 C 表示。<bb N>
是所谓的基本块- 语句的集合,在程序的工作流程中被视为单个块。人们看到的第一件事是并行区域被提取到一个单独的函数中。这并不少见——大多数 OpenMP 实现或多或少都进行了相同的代码转换。还可以观察到编译器插入了对和之类的函数的调用,这些libgomp
函数用于引导然后完成并行区域的执行(稍后将删除前缀)。里面有一个循环,实现在GOMP_parallel_start
GOMP_parallel_end
__builtin_
fun._omp_fn.0
for
<bb 4>
(注意循环本身也被扩展了)。此外,所有共享变量都被放入一个特殊的结构中,该结构被传递给并行区域的实现。<bb 3>
包含计算当前线程将运行的迭代范围的代码。
嗯,不完全是 C 代码,但这可能是最接近 GCC 的东西。
我还没有用openmp测试过。但是编译器选项-E
应该在预处理后为您提供代码。