我认为这个答案充分描述了原因,但我会在这里扩展一下。
然而,在此之前,这里是gcc 4.8 的文档-fopenmp
:
-fopenmp
在 C/C++ 中启用 OpenMP 指令 #pragma omp 和 Fortran 中的 !$omp 处理。指定 -fopenmp 时,编译器根据 OpenMP 应用程序接口 v3.0 http://www.openmp.org/生成并行代码。此选项暗示 -pthread,因此仅在支持 -pthread 的目标上受支持。
请注意,它没有指定禁用任何功能。事实上,gcc 没有理由禁用任何优化。
然而,为什么具有 1 个线程的 openmp 相对于没有 openmp 具有开销的原因是编译器需要转换代码,添加函数,以便它可以为具有 n>1 个线程的 openmp 的情况做好准备。所以让我们想一个简单的例子:
int *b = ...
int *c = ...
int a = 0;
#omp parallel for reduction(+:a)
for (i = 0; i < 100; ++i)
a += b[i] + c[i];
这段代码应该被转换成这样的:
struct __omp_func1_data
{
int start;
int end;
int *b;
int *c;
int a;
};
void *__omp_func1(void *data)
{
struct __omp_func1_data *d = data;
int i;
d->a = 0;
for (i = d->start; i < d->end; ++i)
d->a += d->b[i] + d->c[i];
return NULL;
}
...
for (t = 1; t < nthreads; ++t)
/* create_thread with __omp_func1 function */
/* for master thread, don't create a thread */
struct master_data md = {
.start = /*...*/,
.end = /*...*/
.b = b,
.c = c
};
__omp_func1(&md);
a += md.a;
for (t = 1; t < nthreads; ++t)
{
/* join with thread */
/* add thread_data->a to a */
}
现在,如果我们使用 运行它nthreads==1
,代码实际上会简化为:
struct __omp_func1_data
{
int start;
int end;
int *b;
int *c;
int a;
};
void *__omp_func1(void *data)
{
struct __omp_func1_data *d = data;
int i;
d->a = 0;
for (i = d->start; i < d->end; ++i)
d->a += d->b[i] + d->c[i];
return NULL;
}
...
struct master_data md = {
.start = 0,
.end = 100
.b = b,
.c = c
};
__omp_func1(&md);
a += md.a;
那么无openmp版本和单线程openmp版本有什么区别呢?
一个区别是有额外的胶水代码。需要传递给openmp创建的函数的变量需要放在一起形成一个参数。所以有一些开销准备函数调用(以及稍后检索数据)
然而,更重要的是,现在代码不再是一体的。跨功能优化还没有那么先进,大多数优化都是在每个功能内完成的。较小的功能意味着优化的可能性较小。
为了完成这个答案,我想向您确切展示-fopenmp
影响gcc
的选项。(注意:我现在在一台旧电脑上,所以我有 gcc 4.4.3)
运行gcc -Q -v some_file.c
给出了这个(相关的)输出:
GGC heuristics: --param ggc-min-expand=98 --param ggc-min-heapsize=128106
options passed: -v a.c -D_FORTIFY_SOURCE=2 -mtune=generic -march=i486
-fstack-protector
options enabled: -falign-loops -fargument-alias -fauto-inc-dec
-fbranch-count-reg -fcommon -fdwarf2-cfi-asm -fearly-inlining
-feliminate-unused-debug-types -ffunction-cse -fgcse-lm -fident
-finline-functions-called-once -fira-share-save-slots
-fira-share-spill-slots -fivopts -fkeep-static-consts -fleading-underscore
-fmath-errno -fmerge-debug-strings -fmove-loop-invariants
-fpcc-struct-return -fpeephole -fsched-interblock -fsched-spec
-fsched-stalled-insns-dep -fsigned-zeros -fsplit-ivs-in-unroller
-fstack-protector -ftrapping-math -ftree-cselim -ftree-loop-im
-ftree-loop-ivcanon -ftree-loop-optimize -ftree-parallelize-loops=
-ftree-reassoc -ftree-scev-cprop -ftree-switch-conversion
-ftree-vect-loop-version -funit-at-a-time -fvar-tracking -fvect-cost-model
-fzero-initialized-in-bss -m32 -m80387 -m96bit-long-double
-maccumulate-outgoing-args -malign-stringops -mfancy-math-387
-mfp-ret-in-387 -mfused-madd -mglibc -mieee-fp -mno-red-zone -mno-sse4
-mpush-args -msahf -mtls-direct-seg-refs
并运行gcc -Q -v -fopenmp some_file.c
给出这个(相关的)输出:
GGC heuristics: --param ggc-min-expand=98 --param ggc-min-heapsize=128106
options passed: -v -D_REENTRANT a.c -D_FORTIFY_SOURCE=2 -mtune=generic
-march=i486 -fopenmp -fstack-protector
options enabled: -falign-loops -fargument-alias -fauto-inc-dec
-fbranch-count-reg -fcommon -fdwarf2-cfi-asm -fearly-inlining
-feliminate-unused-debug-types -ffunction-cse -fgcse-lm -fident
-finline-functions-called-once -fira-share-save-slots
-fira-share-spill-slots -fivopts -fkeep-static-consts -fleading-underscore
-fmath-errno -fmerge-debug-strings -fmove-loop-invariants
-fpcc-struct-return -fpeephole -fsched-interblock -fsched-spec
-fsched-stalled-insns-dep -fsigned-zeros -fsplit-ivs-in-unroller
-fstack-protector -ftrapping-math -ftree-cselim -ftree-loop-im
-ftree-loop-ivcanon -ftree-loop-optimize -ftree-parallelize-loops=
-ftree-reassoc -ftree-scev-cprop -ftree-switch-conversion
-ftree-vect-loop-version -funit-at-a-time -fvar-tracking -fvect-cost-model
-fzero-initialized-in-bss -m32 -m80387 -m96bit-long-double
-maccumulate-outgoing-args -malign-stringops -mfancy-math-387
-mfp-ret-in-387 -mfused-madd -mglibc -mieee-fp -mno-red-zone -mno-sse4
-mpush-args -msahf -mtls-direct-seg-refs
比较一下,我们可以看到唯一的区别是-fopenmp
我们已经-D_REENTRANT
定义了(当然也-fopenmp
启用了)。所以,请放心,gcc 不会产生更糟糕的代码。只是它需要为线程数大于1时添加准备代码并且有一些开销。
更新:我真的应该在启用优化的情况下对此进行测试。无论如何,使用 gcc 4.7.3,添加相同命令的输出-O3
将给出相同的差异。因此,即使使用-O3
,也没有禁用优化。