OpenMP: предложения nowait и reduction в одной и той же прагме

Я изучаю OpenMP и наткнулся на следующий пример:

#pragma omp parallel shared(n,a,b,c,d,sum) private(i)
{
    #pragma omp for nowait
    for (i=0; i<n; i++)
        a[i] += b[i];

    #pragma omp for nowait
    for (i=0; i<n; i++)
        c[i] += d[i];
    #pragma omp barrier

    #pragma omp for nowait reduction(+:sum)
    for (i=0; i<n; i++)
        sum += a[i] + c[i];
} /*-- End of parallel region --*/

в последнем цикле for есть предложение nowait и reduction. Правильно ли это? Разве предложение сокращения не должно быть синхронизировано?

3 ответов


на nowaits во втором и последнем цикле несколько избыточны. Спецификация OpenMP упоминает nowait до конца региона, поэтому, возможно, это может остаться.

но nowait перед вторым циклом и явным барьером после него отменяют друг друга.

и наконец, о shared и private положения. В коде shared не имеет никакого эффекта, и private просто не следует использовать вообще: если вам нужна переменная thread-private, просто объявите ее внутри параллельной области. В частности, вы должны объявить переменные цикла внутри цикла, не раньше.

сделать shared полезно, вам нужно сказать OpenMP, что он не должен делиться ничем по умолчанию. Вы должны сделайте это, чтобы избежать ошибок из-за случайно общих переменных. Это делается путем указания default(none). Это оставляет нас с:

#pragma omp parallel default(none) shared(n, a, b, c, d, sum)
{
    #pragma omp for nowait
    for (int i = 0; i < n; ++i)
        a[i] += b[i];

    #pragma omp for
    for (int i = 0; i < n; ++i)
        c[i] += d[i];

    #pragma omp for nowait reduction(+:sum)
    for (int i = 0; i < n; ++i)
        sum += a[i] + c[i];
} // End of parallel region

в некоторых отношениях это похоже на домашнюю работу, которую я ненавижу делать для людей. С другой стороны, приведенные выше ответы не совсем точны, и я считаю, что их следует исправить.

во-первых, хотя в этом примере как общие, так и частные предложения не нужны, я не согласен с Конрадом, что их не следует использовать. Одна из наиболее распространенных проблем, связанных с распараллеливанием кода, заключается в том, что люди не тратят время на понимание того, как используются переменные. Не приватизация и / или защита общих переменных, которые должны быть, составляют наибольшее количество проблем, которые я вижу. Прохождение упражнения по изучению того, как используются переменные, и помещению их в соответствующие общие, частные и т. д. п. значительно уменьшит количество проблем.

Что касается вопроса о барьерах, первый цикл может иметь предложение nowait, потому что во втором цикле не используется вычисленное значение (a). Второй цикл может иметь предложение nowait только в том случае, если вычисленное значение (c) не используется до вычисления значений (т. е. нет зависимости). В исходном примере кода есть nowait на втором цикле, но явный барьер перед третьим циклом. Это нормально, так как ваш профессор пытался показать использование явного барьера - хотя отключение nowait на втором цикле сделало бы явный барьер избыточным (поскольку в конце цикла есть неявный барьер).

на с другой стороны, nowait на второй петле и явный барьер мая не нужно вообще. До OpenMP V3.0 спецификация, многие люди предполагали, что что-то было истинным, что не было разъяснено в спецификации. С OpenMP V3.0 спецификация в раздел 2.5.1 была добавлена следующая конструкция цикла, таблица 2-1 предложение расписания вид значения, статический (график):

совместимая реализация статического расписания должна обеспечить что же назначение логических чисел итерации потокам будет использоваться в двух циклах регионы, если выполняются следующие условия: 1) обе области цикла имеют одинаковое количество итераций цикла, 2) обе области цикла имеют одинаковое значение chunk_size указано, или обе области цикла не имеют chunk_size указано, и 3) обе области цикла связываются с одной и той же параллельной областью. Зависимость по данным между гарантируется, что будут выполнены одинаковые логические итерации в двух таких циклах разрешение безопасного использования предложения nowait (см. раздел A. 9 на стр. 170 для образцы.)

теперь в вашем примере расписание не было показано ни на одном из циклов, поэтому это может или не может выполняться. Причина в том, что расписание по умолчанию определено реализацией, и хотя большинство реализаций в настоящее время определяют расписание по умолчанию как статическое, нет никакой гарантии этого. Если бы ваш профессор поставил на график тип статики без размер куска на всех трех циклы, тогда nowait можно было бы использовать на первом и втором цикле, и никакой барьер (неявный или явный) не потребуется между вторым и третьим циклами вообще.

теперь мы переходим к третьему циклу и вашему вопросу о nowait и сокращении. Как отметил Мичи, спецификация OpenMP позволяет указывать как (сокращение, так и nowait). Однако неверно, что для завершения сокращения не требуется никакой синхронизации. В Примере неявный барьер (на конец третьего цикла) можно удалить с помощью nowait. Это происходит потому, что сокращение (сумма) не используется до того, как неявный барьер параллельной области был обнаружен.

Если вы посмотрите на OpenMP V3.0 спецификация, раздел 2.9.3.6 предложение сокращения, вы найдете следующее:

Если nowait не используется, вычисление сокращения будет завершено в конце построить, однако, если сокращение предложения при создании в какой параметр NOWAIT также применяется, доступ к исходному элементу списка создаст гонку и, таким образом, будет иметь неопределенный эффект, если синхронизация не гарантирует, что они происходят после всех потоков выполнены все их итерации или конструкции разделов, а также вычисление сокращения завершено и сохранено вычисленное значение этого элемента списка. Это может быть проще всего обеспечено через синхронизацию барьера.

Это означает, что если вы хотите использовать переменную sum в параллельная область после третьего цикла, тогда вам понадобится барьер (неявный или явный), прежде чем вы его используете. Как показывает пример, это правильно.


на OpenMP speficication говорит:

синтаксис конструкции цикла выглядит следующим образом:

#pragma omp for [clause[[,] clause] ... ] new-line
    for-loops

where является одним из следующих:

 ...
 reduction(operator: list)
 ...
 nowait

таким образом, может быть больше предложений, таким образом, может быть как сокращение, так и оператор nowait.

нет необходимости в явной синхронизации в reduction предложение - добавлять к sum переменная синхронизируется из-за reduction(+: sum) и предыдущие барьерные силы a и b имея конечные значения во время reduction петли. The nowait означает, что если поток завершает работу в цикле, ему не нужно ждать, пока все остальные потоки закончат тот же цикл.