#c #parallel-processing #openmp
#c #параллельная обработка #openmp
Вопрос:
У меня очень странная проблема с использованием OpenMP в моем коде на C :
void update(double *source, double *target, int n)
{
target[0] = source[0];
target[n-1] = source[n-1];
#pragma omp parallel for
for(int i = 1; i < n-1; i)
target[i] = (1.0/3.0) * (source[i-1] source[i] source[i 1]);
}
И исходный, и целевой являются двойными массивами с n элементами. Код отлично работает при использовании его без OpenMP. Но как только я использую pragma, код, похоже, застревает в этом цикле. Дело в том, что я абсолютно понятия не имею, почему. Надеюсь, кто-нибудь может мне помочь
Комментарии:
1. Компилируется и отлично запускается с GCC 4.6.1. Какой компилятор вы используете?
2. Можете ли вы добавить некоторые подробности о компиляторе и операционной системе?
3. gcc 4.2.1 в macOS Snow Leopard, точнее: 686-apple-darwin10-g -4.2.1 (GCC) 4.2.1 (Apple Inc., сборка 5666) (точка 3)
4. Ну, я только что узнал, что цикл все еще работает… он просто становится очень-очень-очень медленным — я пробовал его с гораздо меньшими размерами, и версия OpenMP в 100-1000 раз медленнее, чем версия unparallelize
Ответ №1:
Насколько велико значение n?
Планирование по умолчанию для директивы OpenMP parallel for
зависит от конкретной реализации. Похоже, что в GOMP (реализация OpenMP, используемая gcc) по умолчанию используется, (dynamic,1)
согласно документации здесь. Это означает, что каждый поток обращается к ячейкам памяти (в i-1
и i 1
), которые загружены соседними потоками, что может привести к плохому использованию кэша. В современных архитектурах ЦП подобные трафаретные операции часто ограничены памятью и чувствительны к кэшированию. Вы могли бы попробовать указать расписание с большими фрагментами, например с:
#pragma omp parallel for schedule(dynamic,1024)
Я просто использую 1024 здесь в качестве примера. На практике вам следует поэкспериментировать, чтобы найти оптимальный коэффициент фрагментации (или систематически выполнять поиск с помощью развертки параметров, процесс, часто называемый «автоматической настройкой»). Или вы могли бы выбрать значение, основанное больше на теории, например, выведя его из размера кэша L1 или L2 вашего процессора.
Или вы могли бы вместо этого попробовать статическое планирование, поскольку объем вычислений внутри цикла for одинаков для всех потоков, и накладные расходы динамического планировщика могут быть причиной узкого места. Если вы укажете
#pragma omp parallel for schedule(static)
без указания размера блока каждому потоку будет назначен отдельный блок примерно того же размера.
Наконец, вы также можете захотеть привязать потоки OpenMP к их собственным ядрам процессора. Вы можете сделать это, используя переменную окружения GOMP_CPU_AFFINITY.
Редактировать:
Я просто поиграл со следующей тестовой программой, скомпилированной с помощью gcc 4.2.1, и я думаю, что документация, на которую я ссылался выше, неверна. Похоже, что GOMP по умолчанию имеет значение schedule(static)
.
#include <stdio.h>
#include <omp.h>
int main(int argc, char** argv)
{
int i;
#pragma omp parallel for
for (i=0; i<15; i ) {
int id = omp_get_thread_num();
printf("%d assigned to thread %dn", i, id);
}
}
И вывод с двумя потоками равен:
$ ./test_sched | sort -n
0 assigned to thread 0
1 assigned to thread 0
2 assigned to thread 0
3 assigned to thread 0
4 assigned to thread 0
5 assigned to thread 0
6 assigned to thread 0
7 assigned to thread 0
8 assigned to thread 1
9 assigned to thread 1
10 assigned to thread 1
11 assigned to thread 1
12 assigned to thread 1
13 assigned to thread 1
14 assigned to thread 1
Комментарии:
1. Значение по умолчанию, когда планирование не определено, действительно зависит от реализации, но AFAIK обычно является
static
. Ссылка GOMP, предполагающая, что(dynamic, 1)
используется по умолчанию, на самом деле относится к случаю, в которомschedule(runtime)
используется.