Векторное заполнение в потоках OpenMP
У меня есть алгоритм, для которого одна цель-заполнить векторы. Для повышения производительности итерации алгоритма распределяются по потокам OpenMP. Мне было интересно, какой способ обеспечит лучший/безопасный способ заполнения векторов.
Обратите внимание, что порядок векторов должен быть последовательным (т. е. значение n век1 должно исходить из той же итерации, что и значение n век2.)
Гипотеза 1:
std::vector<BasicType> vec1;
std::vector<BasicType> vec2;
#pragma opm parallel for
for(...)
{
// Do some intensive stuff to compute val1 and val2
// ...
#pragma omp critical
{
vec1.push_back(val1);
vec2.push_back(val2);
}
}
// Then go on to work with vec1 and vec2...
Гипотеза 2:
std::vector<BasicType> vec1;
std::vector<BasicType> vec2;
#pragma opm parallel
{
std::vector<BasicType> vec1local;
std::vector<BasicType> vec2local;
#pragma omp for
for(...)
{
// Do some intensive stuff to compute val1 and val2
// ...
vec1local.push_back(val1);
vec2local.push_back(val2);
}
#pragma omp critical
{
// See note 1 below
vec1.insert(vec1.end(), vec1local.begin(), vec1local.end());
vec2.insert(vec2.end(), vec2local.begin(), vec2local.end());
}
}
// Then go on to work with vec1 and vec2...
Примечание 1: это было взято из лучший способ добавить вектор к вектору
примечание 2: кажется, что val1 и val2 могут быть объединены в некотором объекте, чтобы поддерживать согласованность и работать только с одним вектором, но пока это кажется непрактичным для остальной части алгоритма.
Примечание 3: чтобы дать порядок величины, цикл for содержит около 100 итераций, разбитых на 4 потока. За исключением очень немногих исключений, ожидается, что каждая итерация будет иметь та же рабочая нагрузка (что приводит к тому, что проблема критических разделов происходит примерно в одно и то же время.)
примечание 4: только для записей, все это имеет дело со стабилизацией изображения и работает на архитектуре Tegra K1. Используемый компилятор-gcc 4.8.4.
2 ответов:
Я предполагаю, что вам нужно использовать вектор и нельзя использовать массив (в противном случае ваш вопрос не очень интересен). Используя
t = omp_get_num_threads(), Вы заполняете векторы параллельно, а затем объединяете их в операцииlog2(t)вместо операцийt(Как вы делаете сейчас), как этоvoid reduce(std::vector<BasicType> *v1, std::vector<BasicType> *v2, int begin, int end) { if(end - begin == 1) return; int pivot = (begin+end)/2; #pragma omp task reduce(v, begin, pivot); #pragma omp task reduce(v, pivot, end); #pragma omp taskwait v1[begin].insert(v1[begin].end(), v1[pivot].begin(), v1[pivot].end()); v2[begin].insert(v2[begin].end(), v2[pivot].begin(), v2[pivot].end()); }И
std::vector<BasicType> v1, v2; std::vector<BasicType> *v1p, *v2p; #pragma omp parallel { #pragma omp single { v1p = new std::vector<BasicType>[omp_get_num_threads()]; v2p = new std::vector<BasicType>[omp_get_num_threads()]; } #pragma omp for for(...) { // Do some intensive stuff to compute val1 and val2 // ... v1p[omp_get_thread_num()].push_back(val1); v2p[omp_get_thread_num()].push_back(val2); } #pragma omp single reduce(v1p, v2p, 0, omp_get_num_threads()); } v1 = v1p[0], v2 = v2p[0]; delete[] v1p; delete[] v2p;Например с 8 потоками это соединит векторы для потоков
Дополнительную информацию о параллельном заполнении векторов смотрите в разделеthis . Для большего информацию о слиянии потоков в операциях(0,1) (2,3) (4,5) (6,7) (0,2) (4,6) (0,4)log2(t)смотрите в ответе на этот вопрос.
Из ваших двух предложений я бы предпочел первое. Он проще и не требует дополнительной памяти. Однако я бы предложил альтернативу без раздела
critical:std::vector<BasicType> vec1(size); std::vector<BasicType> vec2(size); #pragma opm parallel for for(...) { // Do some intensive stuff to compute val1 and val2 // ... vec1[i] = val1; vec2[i] = val2; }Обратите внимание, что это может иметь некоторые проблемы с производительностью из-за кражи строки кэша. Однако я бы не беспокоился об этом, если бы это не оказалось реальной проблемой, подтвержденной фактическим анализом производительности. Тогда решение может быть следующим:
- переходите ко второму решению. (что стоит памяти и дополнительная постобработка)
- выровняйте вектор и используйте соответствующие фрагменты для цикла. (таким образом, что каждый поток имеет локальные строки кэша)
- используйте структуру данных, которая внутренне содержит локальные векторы, но вовне обеспечивает необходимые глобальные вектор-операции. (Это, вероятно, в целом лучшее решение.)
Comments