У меня есть программа на С++, которая в основном выполняет некоторые вычисления матрицы. Для них я использую LAPACK/BLAS и обычно ссылаюсь на MKL или ACML в зависимости от платформы. Многие из этих матричных вычислений работают на разных независимых матрицах, и поэтому я использую std:: thread, чтобы эти операции выполнялись параллельно. Тем не менее, я заметил, что у меня нет ускорения при использовании большего количества потоков. Я проследил эту проблему до простой процедуры Blas. Кажется, что если два потока используют эту процедуру параллельно, каждый поток занимает в два раза больше времени, хотя оба потока работают на разных массивах.
Следующее, что я попробовал, - это написать новый простой метод для выполнения векторных дополнений для замены подпрограммы daxpy. С помощью одного потока этот новый метод выполняется так же быстро, как и процедура BLAS, но при компиляции с gcc он испытывает те же проблемы, что и процедура BLAS: удвоение числа потоков, выполняемых параллельно, также удваивает количество времени, которое требуется каждому потоку, поэтому ускорение не достигается. Однако с использованием компилятора Intel С++ эти проблемы исчезают: с увеличением числа потоков время, которое требуется одному потоку, постоянно.
Однако мне нужно также скомпилировать системы, где нет компилятора Intel. Поэтому мои вопросы: почему нет ускорения работы с gcc и есть ли возможность улучшить производительность gcc?
Я написал небольшую программу для демонстрации эффекта:
// $(CC) -std=c++11 -O2 threadmatrixsum.cpp -o threadmatrixsum -pthread
#include <iostream>
#include <thread>
#include <vector>
#include "boost/date_time/posix_time/posix_time.hpp"
#include "boost/timer.hpp"
void simplesum(double* a, double* b, std::size_t dim);
int main() {
for (std::size_t num_threads {1}; num_threads <= 4; num_threads++) {
const std::size_t N { 936 };
std::vector <std::size_t> times(num_threads, 0);
auto threadfunction = [&](std::size_t tid)
{
const std::size_t dim { N * N };
double* pA = new double[dim];
double* pB = new double[dim];
for (std::size_t i {0}; i < N; ++i){
pA[i] = i;
pB[i] = 2*i;
}
boost::posix_time::ptime now1 =
boost::posix_time::microsec_clock::universal_time();
for (std::size_t n{0}; n < 1000; ++n){
simplesum(pA, pB, dim);
}
boost::posix_time::ptime now2 =
boost::posix_time::microsec_clock::universal_time();
boost::posix_time::time_duration dur = now2 - now1;
times[tid] += dur.total_milliseconds();
delete[] pA;
delete[] pB;
};
std::vector <std::thread> mythreads;
// start threads
for (std::size_t n {0} ; n < num_threads; ++n)
{
mythreads.emplace_back(threadfunction, n);
}
// wait for threads to finish
for (std::size_t n {0} ; n < num_threads; ++n)
{
mythreads[n].join();
std::cout << " Thread " << n+1 << " of " << num_threads
<< " took " << times[n]<< "msec" << std::endl;
}
}
}
void simplesum(double* a, double* b, std::size_t dim){
for(std::size_t i{0}; i < dim; ++i)
{*(++a) += *(++b);}
}
Outout с gcc:
Thread 1 of 1 took 532msec
Thread 1 of 2 took 1104msec
Thread 2 of 2 took 1103msec
Thread 1 of 3 took 1680msec
Thread 2 of 3 took 1821msec
Thread 3 of 3 took 1808msec
Thread 1 of 4 took 2542msec
Thread 2 of 4 took 2536msec
Thread 3 of 4 took 2509msec
Thread 4 of 4 took 2515msec
Вывод с icc:
Thread 1 of 1 took 663msec
Thread 1 of 2 took 674msec
Thread 2 of 2 took 674msec
Thread 1 of 3 took 681msec
Thread 2 of 3 took 681msec
Thread 3 of 3 took 681msec
Thread 1 of 4 took 688msec
Thread 2 of 4 took 689msec
Thread 3 of 4 took 687msec
Thread 4 of 4 took 688msec
Итак, с icc время, необходимое для одного потока, выполняется, вычисления постоянны (как я и ожидал, мой процессор имеет 4 физических ядра), а с gcc время для одного потока увеличивается. Замена процедуры simplesum BLAS:: daxpy дает те же результаты для icc и gcc (неудивительно, так как большинство времени проводится в библиотеке), которые почти такие же, как и указанные выше результаты gcc.