#c #openmp #parallel-processing
#c #openmp #параллельная обработка
Вопрос:
РЕШАЕМАЯ: смотрите РЕДАКТИРОВАНИЕ 2 ниже
Я пытаюсь распараллелить алгоритм, который выполняет некоторую операцию над матрицей (давайте для простоты назовем это размытием). Как только эта операция выполнена, она обнаруживает наибольшее изменение между старой и новой матрицей (максимальная абсолютная разница между старой и новой матрицей на элементной основе). Если эта максимальная разница превышает некоторый порог, то выполните еще одну итерацию операции с матрицей.
Итак, моя основная программа имеет следующий цикл:
converged = 0;
for( i = 1; i <= iteration_limit; i ){
max_diff = update( amp;data_grid );
if( max_diff < tol ) {
converged = 1;
break;
}
}
update( amp;data_grid )
затем вызывает фактическую реализацию алгоритма размытия. Затем алгоритм размытия выполняет итерацию по матрице, именно этот цикл я пытаюсь распараллелить:
for( i = 0; i < width; i ) {
for( j = 0; j <= height; j ) {
g->data[ update ][ i ][ j ] =
ONE_QUARTER * (
g->data[ update ][ i 1 ][ j ]
g->data[ update ][ i - 1 ][ j ]
g->data[ update ][ i ][ j 1 ]
g->data[ update ][ i ][ j - 1 ]
);
diff = fabs( g->data[ old ][ i ][ j ] - g->data[ update ][ i ][ j ] );
maxdiff = maxdiff > diff ? maxdiff : diff;
}
}
Я мог бы просто вставить параллельную область внутрь update(amp;data_grid)
но это означало бы, что потоки будут создаваться и уничтожаться на каждой итерации, чего я пытаюсь избежать.:
#pragma omp parallel for private(i, j, diff, maxdg) shared(width, height, update, g, dg, chunksize) default(none) schedule(static, chunksize)
У меня есть 2 копии сетки, и я записываю новый ответ в «другой» на каждой итерации, переключая old
и update
между 0
и 1
.
Редактировать:
Итак, я создал цикл orphaned omp for в соответствии с предложением Джонатана Дурси, но по какой-то причине, похоже, не могу найти максимальное значение между потоками…
Вот мой «внешний» код:
converged = 0;
#pragma omp parallel shared(i, max_iter, g, tol, maxdg, dg) private(converged) default(none)
{
for( i = 1; i <= 40; i ){
maxdg = 0;
dg = grid_update( amp;g );
printf("[%d] dg from a single thread: %fn", omp_get_thread_num(), dg );
#pragma omp critical
{
if (dg > maxdg) maxdg = dg;
}
#pragma omp barrier
#pragma omp flush
printf("[%d] maxdg: %fn", omp_get_thread_num(), maxdg);
if( maxdg < tol ) {
converged = 1;
break;
}
}
}
И результат:
[11] dg from a single thread: 0.000000
[3] dg from a single thread: 0.000000
[4] dg from a single thread: 0.000000
[5] dg from a single thread: 0.000000
[0] dg from a single thread: 0.166667
[6] dg from a single thread: 0.000000
[7] dg from a single thread: 0.000000
[8] dg from a single thread: 0.000000
[9] dg from a single thread: 0.000000
[15] dg from a single thread: 0.000000
[10] dg from a single thread: 0.000000
[1] dg from a single thread: 0.166667
[12] dg from a single thread: 0.000000
[13] dg from a single thread: 0.000000
[14] dg from a single thread: 0.000000
[2] maxdg: 0.000000
[3] maxdg: 0.000000
[0] maxdg: 0.000000
[8] maxdg: 0.000000
[9] maxdg: 0.000000
[4] maxdg: 0.000000
[5] maxdg: 0.000000
[6] maxdg: 0.000000
[7] maxdg: 0.000000
[1] maxdg: 0.000000
[14] maxdg: 0.000000
[11] maxdg: 0.000000
[15] maxdg: 0.000000
[10] maxdg: 0.000000
[12] maxdg: 0.000000
[13] maxdg: 0.000000
ПРАВКА 2:
Допустил несколько ошибок с частными / общими классификаторами и забыл о барьере. Это правильный код:
#pragma omp parallel shared(max_iter, g, tol, maxdg) private(i, dg, converged) default(none)
{
for( i = 1; i <= max_iter; i ){
#pragma omp barrier
maxdg=0;
/*#pragma omp flush */
dg = grid_update( amp;g );
#pragma omp critical
{
if (dg > maxdg) maxdg = dg;
}
#pragma omp barrier
/*#pragma omp flush*/
if( maxdg < tol ) {
converged = 1;
break;
}
}
}
Комментарии:
1. Вы уверены, что ваша реализация OMP действительно уничтожает потоки после параллельной области? Обычно я ожидаю, что он использует threadpool, чтобы избежать этого.
2. Насколько я могу понять, это так, в любом случае, однако я хотел бы знать, как применить потерянные директивы к этой ситуации.
3. Большинство реализаций фактически используют пул потоков, чтобы уменьшить накладные расходы на уничтожение и повторное получение потоков для каждой новой параллельной области. Тем не менее, каждый раз, когда вы входите в параллельную область, все еще возникает изрядная нагрузка — хотя первая область обычно будет самой дорогой (поскольку она создает потоки). Обратите внимание — что это зависит от реализации относительно того, когда потоки действительно впервые получены — это может быть до того, как пользовательский код получит управление, или первая параллельная область, или …
Ответ №1:
Нет проблем с запуском параллельной секции в другой процедуре перед for, конечно, начиная с OpenMP 3.0 (2008) и, возможно, начиная с OpenMP 2.5. С gcc4.4:
outer.c:
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
void update(int n, int iter);
int main(int argc, char **argv) {
int n=10;
#pragma omp parallel num_threads(4) default(none) shared(n)
for (int iter=0; iter<3; iter )
{
#pragma omp single
printf("---iteration %d---n", iter);
update(n, iter);
}
return 0;
}
inner.c:
#include <omp.h>
#include <stdio.h>
void update(int n, int iter) {
int thread = omp_get_thread_num();
#pragma omp for
for (int i=0;i<n;i ) {
int newthread=omp_get_thread_num();
printf("=: doing loop index %d.n",newthread,i);
}
}
Построение:
$ make
gcc44 -g -fopenmp -std=c99 -c -o inner.o inner.c
gcc44 -g -fopenmp -std=c99 -c -o outer.o outer.c
gcc44 -o main outer.o inner.o -fopenmp -lgomp
$ ./main
---iteration 0---
2: doing loop index 6.
2: doing loop index 7.
2: doing loop index 8.
0: doing loop index 0.
0: doing loop index 1.
0: doing loop index 2.
1: doing loop index 3.
1: doing loop index 4.
1: doing loop index 5.
3: doing loop index 9.
---iteration 1---
0: doing loop index 0.
0: doing loop index 1.
0: doing loop index 2.
1: doing loop index 3.
1: doing loop index 4.
1: doing loop index 5.
3: doing loop index 9.
2: doing loop index 6.
2: doing loop index 7.
2: doing loop index 8.
---iteration 2---
0: doing loop index 0.
0: doing loop index 1.
0: doing loop index 2.
3: doing loop index 9.
2: doing loop index 6.
2: doing loop index 7.
2: doing loop index 8.
1: doing loop index 3.
1: doing loop index 4.
1: doing loop index 5.
Но, согласно @jdv-Яну де Ваану, я был бы очень удивлен, если бы в современной реализации OpenMP это привело к значительному повышению производительности по сравнению с параллельным обновлением for, особенно если обновление достаточно дорогое.
Кстати, есть проблемы с простым размещением параллельного for вокруг i-цикла в подпрограмме Гаусса-Зайделя в обновлении; вы можете видеть, что шаги i не являются независимыми, и это приведет к условиям гонки. Вместо этого вам нужно будет сделать что-то вроде Red-Black или итерации Jacobi…
Обновить:
Приведенный пример кода предназначен для итерации G-S, а не Jacobi, но я просто предположу, что это опечатка.
Если ваш вопрос на самом деле касается сокращения, а не осиротевшего цикла for: да, вам, к сожалению, приходится выполнять собственные сокращения min / max в OpenMP, но это довольно просто, вы просто используете обычные приемы.
Обновление 2 — блин, locmax должен быть приватным, а не общим.
outer.c:
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
int update(int n, int iter);
int main(int argc, char **argv) {
int n=10;
int max, locmax;
max = -999;
#pragma omp parallel num_threads(4) default(none) shared(n, max) private(locmax)
for (int iter=0; iter<3; iter )
{
#pragma omp single
printf("---iteration %d---n", iter);
locmax = update(n, iter);
#pragma omp critical
{
if (locmax > max) max=locmax;
}
#pragma omp barrier
#pragma omp flush
#pragma omp single
printf("---iteration %d's max value = %d---n", iter, max);
}
return 0;
}
inner.c:
#include <omp.h>
#include <stdio.h>
int update(int n, int iter) {
int thread = omp_get_thread_num();
int max = -999;
#pragma omp for
for (int i=0;i<n;i ) {
printf("=: doing loop index %d.n",thread,i);
if (i iter>max) max = i iter;
}
return max;
}
и построение:
$ make
gcc44 -g -fopenmp -std=c99 -c -o inner.o inner.c
gcc44 -g -fopenmp -std=c99 -c -o outer.o outer.c
gcc44 -o main outer.o inner.o -fopenmp -lgomp
bash-3.2$ ./main
---iteration 0---
0: doing loop index 0.
0: doing loop index 1.
0: doing loop index 2.
2: doing loop index 6.
2: doing loop index 7.
2: doing loop index 8.
1: doing loop index 3.
1: doing loop index 4.
1: doing loop index 5.
3: doing loop index 9.
---iteration 0's max value = 9---
---iteration 1---
0: doing loop index 0.
0: doing loop index 1.
0: doing loop index 2.
3: doing loop index 9.
2: doing loop index 6.
2: doing loop index 7.
2: doing loop index 8.
1: doing loop index 3.
1: doing loop index 4.
1: doing loop index 5.
---iteration 1's max value = 10---
---iteration 2---
0: doing loop index 0.
0: doing loop index 1.
0: doing loop index 2.
1: doing loop index 3.
1: doing loop index 4.
1: doing loop index 5.
3: doing loop index 9.
2: doing loop index 6.
2: doing loop index 7.
2: doing loop index 8.
---iteration 2's max value = 11---
Комментарии:
1. На самом деле я использую итерацию Jacobi, моя главная проблема в том, как мне найти максимальную разницу между всеми различными потоками в итерации? Я использую C, поэтому, к сожалению, не могу использовать reduce.
2. @Jonathan — Я не верю, что вам нужно указывать оба -fopenmp и -lgomp в последней строке. Опция -fopenmp выполняет несколько действий, включая добавление -lgomp в команду ld, вызывающую проблемы gcc.
3. Скоро будут сокращены минимальные и максимальные значения для C / C — в ближайшей к вам реализации OpenMP версии V3.1. Между тем, не так сложно «создать свой собственный». Здесь есть несколько обсуждений о них в stackoverflow и на openmp.org/forum .
4. @Jonathan Я последовал вашим предложениям, но по какой-то причине все еще не могу найти максимальное значение. Я просто туплю?
5. Вероятно, нет, но вам придется быть более конкретным, чем это. Что не работает? Вы получаете только локальный максимум?