Объяснение flush direcitve с OpenMP: когда это необходимо и когда это полезно

Одна директива OpenMP, которую я никогда не использовал и не знаю, когда ее использовать, flush(со списком и без него).

У меня есть два вопроса:

1.) When is an explicit `omp flush` or `omp flush(var1, ...) necessary?  
2.) Is it sometimes not necessary but helpful (i.e. can it make the code fast)?

Основная причина, по которой я не могу понять, когда использовать явный сброс, заключается в том, что сброс выполняется неявно после многих директив (например, как барьер, одиночный...), которые синхронизируют потоки. Я не могу, например, увидеть способ использования сброса и не синхронизации (например, с nowait) было бы полезно.

Я понимаю, что разные компиляторы могут реализовывать omp flush по-разному. Некоторые могут интерпретировать сброс со списком как один без (т. Е. Очистить все общие объекты). OpenMP flush vs flush (list). Но я забочусь только о том, что требует спецификация. Другими словами, я хочу знать, где явное flush в принципе может быть необходимым или полезным.

Изменить: я думаю, мне нужно уточнить мой второй вопрос. Позвольте мне привести пример. Я хотел бы знать, бывают ли случаи, когда удаление неявного сброса (например, с nowait) и использование вместо этого явного сброса, но только для определенных общих переменных, будет быстрее (и все же даст правильный результат). Что-то вроде следующего:

float a,b;
#pragma omp parallel
{
    #pragma omp for nowait // No barrier.  Do not flush on exit.
        //code which uses only shared variable a        
    #pragma  omp flush(a) // Flush only variable a rather than all shared variables.       
    #pragma omp for
       //Code which uses both shared variables a and b.
}

Я думаю, что коду по-прежнему нужен барьер после первого цикла for, но все барьеры имеют неявный сброс, что противоречит цели. Возможно ли иметь барьер, который не делает флеш?

2 ответа

Решение

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

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

Общая рекомендация заключается в том, что не следует использовать промывки. Если вообще, программисты должны избегать очистки со списком (flush(var,...)) любым способом. Некоторые люди на самом деле говорят об отказе от него в будущем OpenMP.

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

РЕДАКТИРОВАТЬ: Для вашего второго вопроса ответ - нет. OpenMP гарантирует, что каждый поток имеет согласованное представление о разделяемой памяти, когда это необходимо. Если потоки не синхронизируются, им не нужно обновлять свой взгляд на разделяемую память, так как они не видят там никаких "интересных" изменений. Это означает, что любое чтение потока не считывает данные, которые были изменены каким-либо другим потоком. Если бы это было так, то у вас было бы состояние гонки и потенциальная ошибка в вашей программе. Чтобы избежать гонки, вам нужно синхронизировать (что затем подразумевает сброс, чтобы снова сделать представление каждого участвующего потока снова согласованным). Аналогичный аргумент относится к барьерам. Вы используете барьеры, чтобы начать новую эпоху в вычислении параллельной области. Поскольку вы держите потоки в режиме блокировки, вы, скорее всего, также будете иметь некоторое общее состояние между потоками, которое было вычислено в предыдущей эпохе.

Кстати, OpenMP может хранить личные данные для потока, но это не обязательно. Таким образом, вполне вероятно, что компилятор OpenMP некоторое время будет хранить переменные в регистрах, что приводит к их несинхронизации с общей памятью. Однако обновления элементов массива обычно довольно быстро отражаются в общей памяти, поскольку объем частного хранилища для потока обычно невелик (наборы регистров, кэши, чистая память и т. Д.). OpenMP только дает вам некоторые слабые ограничения на то, что вы можете ожидать. Реальная реализация OpenMP (или аппаратное обеспечение) может быть настолько строгой, насколько это желательно (например, немедленно записывать любые изменения и постоянно сбрасывать).

Ура, майкл

Не совсем ответ, но вопрос Майкла Клемма закрыт для комментариев. Я думаю, что отличным примером того, почему сбросы так трудно понять и правильно использовать, является следующий, скопированный (и немного сокращенный) из примеров OpenMP:

//http://www.openmp.org/wp-content/uploads/openmp-examples-4.0.2.pdf
//Example mem_model.2c, from Chapter 2 (The OpenMP Memory Model)
int main() {
   int data, flag = 0;
   #pragma omp parallel num_threads(2)
   {
      if (omp_get_thread_num()==0) {
         /* Write to the data buffer that will be read by thread */
         data = 42;
         /* Flush data to thread 1 and strictly order the write to data
            relative to the write to the flag */
         #pragma omp flush(flag, data)
         /* Set flag to release thread 1 */
         flag = 1;
         /* Flush flag to ensure that thread 1 sees S-21 the change */
         #pragma omp flush(flag)
      }
      else if (omp_get_thread_num()==1) {
         /* Loop until we see the update to the flag */
         #pragma omp flush(flag, data)
         while (flag < 1) {
            #pragma omp flush(flag, data)
         }
         /* Values of flag and data are undefined */
         printf("flag=%d data=%d\n", flag, data);
         #pragma omp flush(flag, data)
         /* Values data will be 42, value of flag still undefined */
         printf("flag=%d data=%d\n", flag, data);
      }
   }
   return 0;
}

Прочитайте комментарии и постарайтесь понять.

Другие вопросы по тегам