gpt4 book ai didi

c++ - 多线程可能会抑制编译器优化吗?

转载 作者:IT老高 更新时间:2023-10-28 21:57:44 28 4
gpt4 key购买 nike

在我身上发生过几次使用 OpenMP 并行化部分程序只是为了注意到最后,尽管具有良好的可扩展性,但由于单线程情况的性能不佳,大部分可预见的加速都失去了(如果与串行版本相比)。

对于这种行为,网络上出现的通常解释是编译器生成的代码在多线程情况下可能会更糟。无论如何,我无法在任何地方找到解释为什么程序集可能更糟的引用。

所以,我想问编译器的人:

编译器优化会被多线程抑制吗?万一,性能会受到怎样的影响?

如果它可以帮助缩小我主要对高性能计算感兴趣的问题。

免责声明:如评论中所述,下面的部分答案可能会在将来过时,因为它们简要讨论了在提出问题时编译器处理优化的方式。

最佳答案

我认为 this answer充分描述了原因,但我将在这里扩展一点。

然而,在此之前,gcc 4.8's documentation on -fopenmp :

-fopenmp
Enable handling of OpenMP directives #pragma omp in C/C++ and !$omp in Fortran. When -fopenmp is specified, the compiler generates parallel code according to the OpenMP Application Program Interface v3.0 http://www.openmp.org/. This option implies -pthread, and thus is only supported on targets that have support for -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,也没有禁用优化。

关于c++ - 多线程可能会抑制编译器优化吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16807766/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com