Mas*_*ano 19 c c++ multithreading openmp compiler-optimization
我碰巧有几次将部分程序与OpenMP并行化,只是为了注意到最终,尽管具有良好的可扩展性,但由于单线程情况的性能较差,大多数预见的加速都会丢失(如果与串行版).
网络上出现的这种行为的常见解释是编译器生成的代码在多线程情况下可能更糟糕.无论如何,我无法在任何地方找到解释为什么装配可能更糟的参考.
那么,我想问问编译器的人是:
多线程可以抑制编译器优化吗?万一,性能怎么会受到影响?
如果它可以帮助缩小问题,我主要对高性能计算感兴趣.
免责声明:正如评论中所述,以下部分答案可能在将来过时,因为它们简要讨论了在提出问题时编译器处理优化的方式.
与OMP的显式编译指示相比,编译器只是不知道代码可以由多个线程执行.因此,他们既不能使代码更高效,也不能降低效率.
这在C++中有严重的后果.对于图书馆作者来说,这是一个特别的问题,他们无法合理地预测他们的代码是否会在使用线程的程序中使用.在阅读公共C运行库和标准C++库实现的源代码时非常明显.这样的代码往往充满了整个地方的小锁,以确保代码在线程中使用时仍能正常运行.即使您没有以线程方式实际使用该代码,也需要付费.一个很好的例子是std :: shared_ptr <>.您支付引用计数的原子更新,即使智能指针仅在一个线程中使用过.并且标准没有提供要求非原子更新的方法,添加该功能的提议被拒绝.
而且它在其他方面也是有害的,编译器无法确保您自己的代码是线程安全的.这是完全由你来使线程安全的.很难做到这一点,这种方法一直存在微妙且非常难以诊断的问题.
大问题,不易解决.也许那是件好事,否则每个人都可能成为程序员;)
我认为这个答案充分描述了原因,但我会在这里稍微扩展一下.
不过,在此之前,这里是gcc 4.8的文档-fopenmp
:
-fopenmp
允许在C/C++中处理OpenMP指令#pragma omp,在Fortran中处理!$ omp.指定-fopenmp时,编译器根据OpenMP应用程序接口v3.0 http://www.openmp.org/生成并行代码.此选项意味着-pthread,因此仅在支持-pthread的目标上受支持.
请注意,它未指定禁用任何功能.实际上,gcc没有理由禁用任何优化.
然而,为什么openmp与1个线程相比没有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];
Run Code Online (Sandbox Code Playgroud)
此代码应转换为以下内容:
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 */
}
Run Code Online (Sandbox Code Playgroud)
现在如果我们运行它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;
Run Code Online (Sandbox Code Playgroud)
那么no openmp版本和单线程openmp版本之间有什么区别?
一个区别是有额外的胶水代码.需要传递给openmp创建的函数的变量需要放在一起形成一个参数.因此有一些开销准备函数调用(以及稍后检索数据)
但更重要的是,现在代码不再是一个整体了.跨功能优化还没有那么先进,大多数优化都是在每个功能中完成的.较小的功能意味着优化的可能性较小.
为了完成这个答案,我想向您展示-fopenmp
影响力gcc
的选择.(注意:我现在在旧电脑上,所以我有gcc 4.4.3)
Running 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
Run Code Online (Sandbox Code Playgroud)
并且运行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
Run Code Online (Sandbox Code Playgroud)
采取差异,我们可以看到唯一的区别是-fopenmp
,我们已-D_REENTRANT
定义(当然也已-fopenmp
启用).所以,请放心,gcc不会产生更糟糕的代码.只是它需要为线程数大于1并且有一些开销时添加准备代码.
更新:我真的应该在启用优化的情况下对此进行测试.无论如何,使用gcc 4.7.3,添加的相同命令的输出-O3
将给出相同的差异.因此,即使有-O3
,也没有禁用优化.
归档时间: |
|
查看次数: |
1699 次 |
最近记录: |