Замечание заранее:
Обработка способа разделения цикла «вручную», я считаю, контрпродуктивна (если вы не хотите понять, как работает OpenMP).Вот почему я сначала предлагаю вам использовать более стандартный подход с операцией reduction
.Вы всегда можете убедиться, что он дает одинаковый результат с точки зрения производительности.
Еще одно замечание: использование всего кода omp_
функций не позволит вам скомпилировать его без опции -openmp
.
Скамья
Итак, я набрал следующий код:
Жатки
#include <iostream>
#include <fstream>
#include <omp.h>
#include <cmath>
#include <chrono>
#include <iomanip>
. тестовая функция с очень простой операцией добавления
void test_simple(long long int N, int * ary, double & sum, long long int & elapsed_milli)
{
std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
start = std::chrono::system_clock::now();
double local_sum = 0.0;
#pragma omp parallel
{
#pragma omp for reduction(+:local_sum)
for (long long int i = 0; i < N; i++) {
local_sum += ary[i];
}
}
sum = local_sum;
end = std::chrono::system_clock::now();
elapsed_milli = std::chrono::duration_cast<std::chrono::microseconds>
(end-start).count();
}
. тестовая функция со сложным, интенсивно использующим процессор знаком (x) atan (sqrt (cos (x) ^ 2 + sin (0.5x) ^ 2)
void test_intensive(long long int N, int * ary, double & sum, long long int & elapsed_milli)
{
std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
start = std::chrono::system_clock::now();
double local_sum = 0.0;
#pragma omp parallel
{
double c, s;
#pragma omp for reduction(+:local_sum)
for (long long int i = 0; i < N; i++) {
c = cos(double(ary[i]));
s = sin(double(ary[i])*0.5);
local_sum += atan(sqrt(c*c+s*s));
}
}
sum = local_sum;
end = std::chrono::system_clock::now();
elapsed_milli = std::chrono::duration_cast<std::chrono::microseconds>
(end-start).count();
}
. Основная функция
using namespace std;
int main() {
long long int N = 1073741825,i;
int * ary = new int[N];
srand (0);
for (i = 0; i < N; i++) { ary[i] = rand()-RAND_MAX/2; }
double sum = 0.0;
sum = 0.0;
long long int elapsed_milli;
cout <<"#"<<setw(19)<<"N"<<setw(20)<<"µs"<< endl;
for(i=128; i<N; i=i*2)
{
test_intensive(i, ary, sum, elapsed_milli);
//test_simple(i, ary, sum, elapsed_milli);
cout << setw(20)<<i<<setw(20)<<elapsed_milli << setw(20)<<sum<<endl;
}
}
Скомпилировать (используя icpc)
Последовательная (без OpenMP) версия скомпилирована с:
icpc test_omp.cpp -O3 --std=c++0x
OpenMP (OpenMP) версия скомпилирована с:
icpc test_omp.cpp -O3 --std=c++0x -openmp
Измерение
Измерения времени выполняются с chrono
с использованием high_precision_clock
, а точность предела на моей машине составляет микросекунды, следовательно, используется std::chrono::microseconds
(нетточка ищет более высокую точность)
График для простой операции (ось в логарифмическом масштабе!)
График для сложной операции (оси в логарифмическом масштабе!)
Сделанные выводы
- При первом использовании OpenMP существует смещение (первое
#pragma omp
пересечено), поскольку поток пула должен быть установлен на месте.
Если мы более внимательно посмотрим на «интенсивный случай»'При первом входе в функцию test_
(с i = 128) затраты времени в случае OpenMP намного выше, чем в случае отсутствия OpenMP.Во втором вызове (с i = 256) мы не видим преимущества использования OpenMP, но время согласовано. Мы видим, что мы не наблюдаем масштабируемость с небольшим количеством выборок.Это проще в простом тестовом примере.Другими словами количество операций внутри параллельной секции должно быть достаточно большим, чтобы время, необходимое для управления пулом потоков, было пренебрежимо малым .В противном случае нет смысла делить операцию на потоки.
В этом случае (с процессором, который я использовал) минимальное количество выборок составляет около 100000. Но если бы я использовал 256 потоковоно наверняка будет около 6000000.
- Однако для более ресурсоемких операций с использованием OpenMP можно ускорить работу даже с 1000 семплами (с процессором, который я использовал)
Сводка
- Если вы работаете с кодом OpenMP , попробуйте предварительно настроить поток пула с помощью простой операции с # pragmaпомпа параллельная .В вашем тестовом примере настройка занимает большую часть времени.
- Использование OpenMP является уловкой, только если вы распараллеливаете достаточно интенсивно загружающие ЦП функции (что на самом деле не является простой суммой массива ...). Например, именно по этой причине во вложенных циклах
#pragma omp for
всегда должен находиться во внешнем «возможном» цикле.