Параллельное сокращение в логарифмическом времени

Дано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();
}

Я чувствую, что лучший метод может быть найден с помощью задач, в которых потоки не используются в занятом цикле.

Ответы на вопрос(1)

Ваш ответ на вопрос