openMp: серьезная потеря производительности при вызове общих ссылок динамических массивов
Я пишу симуляцию cfd и хочу распараллелить мой цикл ~10^5 (размер решетки), который является частью функции-члена. Реализация кода openMp проста: я читаю записи общих массивов, выполняю вычисления с частными потоками и, наконец, снова записываю в общий массив. В каждом массиве я получаю доступ только к элементу массива номера цикла, поэтому не ожидаю условия гонки и не вижу причин для сброса. Тестируя ускорение кода (параллельная часть), я обнаружил, что все, кроме одного процессора, работают всего на ~70%. У кого-нибудь есть идеи, как это улучшить?
void class::funcPar(bool parallel){
#pragma omp parallel
{
int one, two, three;
double four, five;
#pragma omp for
for(int b=0; b<lenAr; b++){
one = A[b]+B[b];
C[b] = one;
one += D[b];
E[b] = one;
}
}
}
1 ответ
Несколько пунктов, затем тестовый код, затем обсуждение:
- 10^5 не так много, если каждый предмет
int
, Затраты на запуск нескольких потоков могут быть больше, чем выгоды. - Оптимизация компилятора может быть испорчена при использовании OMP.
- При работе с несколькими операциями на набор памяти циклы могут быть связаны с памятью (т. Е. Процессор тратит время на ожидание доставки запрошенной памяти)
Как и было обещано, вот код:
#include <iostream>
#include <chrono>
#include <Eigen/Core>
Eigen::VectorXi A;
Eigen::VectorXi B;
Eigen::VectorXi D;
Eigen::VectorXi C;
Eigen::VectorXi E;
int size;
void regular()
{
//#pragma omp parallel
{
int one;
// #pragma omp for
for(int b=0; b<size; b++){
one = A[b]+B[b];
C[b] = one;
one += D[b];
E[b] = one;
}
}
}
void parallel()
{
#pragma omp parallel
{
int one;
#pragma omp for
for(int b=0; b<size; b++){
one = A[b]+B[b];
C[b] = one;
one += D[b];
E[b] = one;
}
}
}
void vectorized()
{
C = A+B;
E = C+D;
}
void both()
{
#pragma omp parallel
{
int tid = omp_get_thread_num();
int nthreads = omp_get_num_threads();
int vals = size / nthreads;
int startInd = tid * vals;
if(tid == nthreads - 1)
vals += size - nthreads * vals;
auto am = Eigen::Map<Eigen::VectorXi>(A.data() + startInd, vals);
auto bm = Eigen::Map<Eigen::VectorXi>(B.data() + startInd, vals);
auto cm = Eigen::Map<Eigen::VectorXi>(C.data() + startInd, vals);
auto dm = Eigen::Map<Eigen::VectorXi>(D.data() + startInd, vals);
auto em = Eigen::Map<Eigen::VectorXi>(E.data() + startInd, vals);
cm = am+bm;
em = cm+dm;
}
}
int main(int argc, char* argv[])
{
srand(time(NULL));
size = 100000;
int iterations = 10;
if(argc > 1)
size = atoi(argv[1]);
if(argc > 2)
iterations = atoi(argv[2]);
std::cout << "Size: " << size << "\n";
A = Eigen::VectorXi::Random(size);
B = Eigen::VectorXi::Random(size);
D = Eigen::VectorXi::Random(size);
C = Eigen::VectorXi::Zero(size);
E = Eigen::VectorXi::Zero(size);
auto startReg = std::chrono::high_resolution_clock::now();
for(int i = 0; i < iterations; i++)
regular();
auto endReg = std::chrono::high_resolution_clock::now();
std::cerr << C.sum() - E.sum() << "\n";
auto startPar = std::chrono::high_resolution_clock::now();
for(int i = 0; i < iterations; i++)
parallel();
auto endPar = std::chrono::high_resolution_clock::now();
std::cerr << C.sum() - E.sum() << "\n";
auto startVec = std::chrono::high_resolution_clock::now();
for(int i = 0; i < iterations; i++)
vectorized();
auto endVec = std::chrono::high_resolution_clock::now();
std::cerr << C.sum() - E.sum() << "\n";
auto startPVc = std::chrono::high_resolution_clock::now();
for(int i = 0; i < iterations; i++)
both();
auto endPVc = std::chrono::high_resolution_clock::now();
std::cerr << C.sum() - E.sum() << "\n";
std::cout << "Timings:\n";
std::cout << "Regular: " << std::chrono::duration_cast<std::chrono::microseconds>(endReg - startReg).count() / iterations << "\n";
std::cout << "Parallel: " << std::chrono::duration_cast<std::chrono::microseconds>(endPar - startPar).count() / iterations << "\n";
std::cout << "Vectorized: " << std::chrono::duration_cast<std::chrono::microseconds>(endVec - startVec).count() / iterations << "\n";
std::cout << "Both : " << std::chrono::duration_cast<std::chrono::microseconds>(endPVc - startPVc).count() / iterations << "\n";
return 0;
}
Я использовал Eigen в качестве векторной библиотеки, чтобы помочь доказать ре оптимизацию, о которой я скоро расскажу. Код был скомпилирован в четырех разных режимах оптимизации:
g ++ -fopenmp -std = C++ 11 -Wall -pedantic -pthread -IC: \ usr \ include source.cpp -o a.exe
g ++ -fopenmp -std = C++ 11 -Wall -pedantic -pthread -O1 -IC: \ usr \ include source.cpp -o aO1.exe
g ++ -fopenmp -std = C++ 11 -Wall -pedantic -pthread -O2 -IC: \ usr \ include source.cpp -o aO2.exe
g ++ -fopenmp -std = C++11 -Wall -pedantic -pthread -O3 -I C:\usr\include source.cpp -o aO3.exe
используя g++ (x86_64-posix-sjlj, созданный проектом strawberryperl.com) 4.8.3 под Windows.
обсуждение
Начнем с рассмотрения 10^5 против 10^6 элементов, усредненных в 100 раз без оптимизации.
10^5 (без оптимизации):
Timings:
Regular: 9300
Parallel: 2620
Vectorized: 2170
Both : 910
10^6 (без оптимизации):
Timings:
Regular: 93535
Parallel: 27191
Vectorized: 21831
Both : 8600
Векторизация (SIMD) превосходит OMP с точки зрения ускорения. Вместе мы получаем еще лучшие времена.
Переезд в -O1:
10^5:
Timings:
Regular: 780
Parallel: 300
Vectorized: 80
Both : 80
10^6:
Timings:
Regular: 7340
Parallel: 2220
Vectorized: 1830
Both : 1670
То же, что и без оптимизаций, за исключением того, что время гораздо лучше.
Пропуск вперед до -O3:
10^5:
Timings:
Regular: 380
Parallel: 130
Vectorized: 80
Both : 70
10^6:
Timings:
Regular: 3080
Parallel: 1750
Vectorized: 1810
Both : 1680
Для 10^5 оптимизация по-прежнему козырная. Однако 10^6 дает более быстрое время для циклов OMP, чем векторизация.
Во всех тестах мы получили ускорение x2-x4 для OMP.
Примечание: я изначально запускал тесты, когда у меня был другой процесс с низким приоритетом, использующий все ядра. По какой-то причине это коснулось в основном параллельных тестов, а не других. Убедитесь, что вы правильно рассчитали время.
Заключение
Ваш пример минимального кода не работает так, как заявлено. Такие проблемы, как шаблоны доступа к памяти, могут возникать с более сложными данными. Добавьте достаточно деталей, чтобы точно воспроизвести вашу проблему ( MCVE), чтобы получить лучшую помощь.