Параллельное сокращение в логарифмическом времени
Даноn
частичные суммы можно суммировать все частичные суммы в log2 параллельных шагах. Например, предположим, что есть восемь потоков с восемью частичными суммами:s0, s1, s2, s3, s4, s5, s6, s7
, Это может быть уменьшено вlog2(8) = 3
последовательные шаги как это;
thread0 thread1 thread2 thread4
s0 += s1 s2 += s3 s4 += s5 s6 +=s7
s0 += s2 s4 += s6
s0 += s4
Я хотел бы сделать это с OpenMP, но я не хочу использовать OpenMPreduction
пункт. Я нашел решение, но думаю, что лучшее решение можно найти, используя OpenMP.task
пункт.
Это более общее, чем скалярное сложение. Позвольте мне выбрать более полезный случай: сокращение массива (см.Вот, Вот, а такжеВот для больше о сокращениях массива).
Допустим, я хочу сделать сокращение массива на массивеa
, Вот некоторый код, который заполняет частные массивы параллельно для каждого потока.
int bins = 20;
int a[bins];
int **at; // array of pointers to arrays
for(int i = 0; i<bins; i++) a[i] = 0;
#pragma omp parallel
{
#pragma omp single
at = (int**)malloc(sizeof *at * omp_get_num_threads());
at[omp_get_thread_num()] = (int*)malloc(sizeof **at * bins);
int a_private[bins];
//arbitrary function to fill the arrays for each thread
for(int i = 0; i<bins; i++) at[omp_get_thread_num()][i] = i + omp_get_thread_num();
}
На данный момент у меня есть массив указателей на массивы для каждого потока. Теперь я хочу добавить все эти массивы вместе и записать окончательную сумму вa
, Вот решение, которое я придумал.
#pragma omp parallel
{
int n = omp_get_num_threads();
for(int m=1; n>1; m*=2) {
int c = n%2;
n/=2;
#pragma omp for
for(int i = 0; i<n; i++) {
int *p1 = at[2*i*m], *p2 = at[2*i*m+m];
for(int j = 0; j<bins; j++) p1[j] += p2[j];
}
n+=c;
}
#pragma omp single
memcpy(a, at[0], sizeof *a*bins);
free(at[omp_get_thread_num()]);
#pragma omp single
free(at);
}
Позвольте мне попытаться объяснить, что делает этот код. Давайте предположим, что есть восемь потоков. Давайте определим+=
оператор означает суммирование по массиву. напримерs0 += s1
является
for(int i=0; i<bins; i++) s0[i] += s1[i]
тогда этот код будет делать
n thread0 thread1 thread2 thread4
4 s0 += s1 s2 += s3 s4 += s5 s6 +=s7
2 s0 += s2 s4 += s6
1 s0 += s4
Но этот код не идеален, как мне бы хотелось.
Одна проблема заключается в том, что существует несколько неявных барьеров, которые требуют синхронизации всех потоков. Эти барьеры не должны быть необходимыми. Первый барьер между заполнением массивов и выполнением сокращения. Второй барьер находится в#pragma omp for
декларация в сокращении. Но я не могу использоватьnowait
пункт с этим методом, чтобы удалить барьер.
Другая проблема заключается в том, что есть несколько потоков, которые не нужно использовать. Например с восемью потоками. Первый шаг в сокращении требует только четыре потока, второй шаг - два потока, а последний шаг - только один поток. Тем не менее, этот метод будет включать все восемь потоков в сокращении. Хотя другие потоки в любом случае ничего не делают и должны идти прямо к барьеру и ждать, так что это, вероятно, не большая проблема.
Мой инстинкт заключается в том, что лучший способ можно найти с помощью omptask
пункт. К сожалению, у меня мало опыта сtask
пункт и все мои усилия с этим сделать сокращение лучше, чем то, что я потерпел неудачу.
Может ли кто-нибудь предложить лучшее решение для сокращения логарифмического времени, например, с помощью OpenMP-хtask
статья?
Я нашел метод, который решает проблему с барьером. Это уменьшает асинхронно. Единственная оставшаяся проблема состоит в том, что он все еще помещает потоки, которые не участвуют в сокращении, в занятый цикл. Этот метод использует что-то вроде стека, чтобы помещать указатели в стек (но никогда не выводит их) в критических разделах (это был один из ключейкритические секции не имеют неявных барьеров, Стек работает поочередно, но сокращается параллельно.
Вот рабочий пример.
#include <stdio.h>
#include <omp.h>
#include <stdlib.h>
#include <string.h>
void foo6() {
int nthreads = 13;
omp_set_num_threads(nthreads);
int bins= 21;
int a[bins];
int **at;
int m = 0;
int nsums = 0;
for(int i = 0; i<bins; i++) a[i] = 0;
#pragma omp parallel
{
int n = omp_get_num_threads();
int ithread = omp_get_thread_num();
#pragma omp single
at = (int**)malloc(sizeof *at * n * 2);
int* a_private = (int*)malloc(sizeof *a_private * bins);
//arbitrary fill function
for(int i = 0; i<bins; i++) a_private[i] = i + omp_get_thread_num();
#pragma omp critical (stack_section)
at[nsums++] = a_private;
while(nsums<2*n-2) {
int *p1, *p2;
char pop = 0;
#pragma omp critical (stack_section)
if((nsums-m)>1) p1 = at[m], p2 = at[m+1], m +=2, pop = 1;
if(pop) {
for(int i = 0; i<bins; i++) p1[i] += p2[i];
#pragma omp critical (stack_section)
at[nsums++] = p1;
}
}
#pragma omp barrier
#pragma omp single
memcpy(a, at[2*n-2], sizeof **at *bins);
free(a_private);
#pragma omp single
free(at);
}
for(int i = 0; i<bins; i++) printf("%d ", a[i]); puts("");
for(int i = 0; i<bins; i++) printf("%d ", (nthreads-1)*nthreads/2 +nthreads*i); puts("");
}
int main(void) {
foo6();
}
Я чувствую, что лучший метод может быть найден с помощью задач, в которых потоки не используются в занятом цикле.