Ответ 1
Я думаю, этот ответ достаточно подробно описывает причину, но я немного расширю здесь.
Прежде чем, однако, gcc 4.8 документация на -fopenmp
:
-fopenmp
Включить обработку директив OpenMP #pragma omp в C/С++ и! $Omp в Fortran. Когда задан параметр -fopenmp, компилятор генерирует параллельный код в соответствии с интерфейсом прикладных программ OpenMP v3.0 http://www.openmp.org/. Этот параметр подразумевает -pthread и, следовательно, поддерживается только для целей, поддерживающих -pthread.
Обратите внимание, что он не указывает на отключение любых функций. Действительно, нет никакой причины для gcc отключать любую оптимизацию.
Тем не менее причина, по которой openmp с 1 потоком имеет накладные расходы по отношению к openmp, заключается в том, что компилятор должен преобразовать код, добавив функции, чтобы он был готов для случаев с openmp с n > 1 потоками. Поэтому подумайте о простом примере:
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
Взяв diff, мы видим, что единственное различие заключается в том, что при -fopenmp
мы определили -D_REENTRANT
(и, конечно, -fopenmp
). Итак, будьте уверены, gcc не приведет к ухудшению кода. Просто нужно добавить код подготовки, когда число потоков больше 1, и у него есть некоторые накладные расходы.
Обновление: Я действительно должен был проверить это с включенной оптимизацией. Во всяком случае, с gcc 4.7.3, вывод тех же команд, добавленных -O3
, даст такую же разницу. Таким образом, даже с -O3
отключена оптимизация.