使用 openmp 任务的部分并行循环

Partially parallel loops using openmp tasks

先决条件:

输入:

代码示例:

(此处conf/config/configData变量仅供参考,主要兴趣在value/valueData变量内。)

void loopFunc(const char* config, int* value)
{
    int conf;
    conf = prepare(config);         // independent, does not change “config”
    *value = process(conf, *value); // dependent, takes prev., produce next
    return;
}

int main()
{
    int N = 100;
    char* configData;           // never changes
    int valueData = 0;          // initial value
    …
    for (int i = 0; i < N; i++)
    {
        loopFunc(configData, &valueData);
    }
    …
}

需要:

已提出并实施的内容:

像这样:

(提醒一下,conf/config/configData变量仅供参考,主要兴趣在value/valueData变量内。)

void loopFunc(const char* config, int* value, volatile int *parSync, int iteration)
{
    int conf;
    conf = prepare(config);         // independent, do not change “config”
    while (*parSync != iteration)   // wait for previous to be ready
    {
        #pragma omp taskyield
    }
    *value = process(conf, *value); // dependent, takes prev., produce next
    *parSync = iteration + 1;       // inform next about readiness
    return;
}

int main()
{
    int N = 100;
    char* configData;           // never changes
    int valueData = 0;          // initial value
    volatile int parallelSync = 0;
    …
    omp_set_num_threads(5);
    #pragma omp parallel
    #pragma omp single
    for (int i = 0; i < N; i++)
    {
        #pragma omp task shared(configData, valueData, parallelSync) firstprivate(i)
            loopFunc(configData, &valueData, &parallelSync, i);
    }
    #pragma omp taskwait
    …
}

发生了什么:

失败了。 :)

原因是openmp任务占用了openmp线程。 例如,如果我们定义 5 个 openmp 线程(如上面的代码)。

如果启动的任务中没有i=0的任务(时常发生),执行中的任务永远等待,永远占用线程,i=0的任务永远不会启动。

下一步是什么?

我不知道如何实现所需的计算模式。

当前解

感谢下面@parallelgeek 的想法

int main()
{
    int N = 10;
    char* configData;           // never changes
    int valueData = 0;          // initial value
    volatile int parallelSync = 0;
    int workers;
    volatile int workingTasks = 0;
    ...
    omp_set_num_threads(5);
    #pragma omp parallel
    #pragma omp single
    {
        workers = omp_get_num_threads()-1;  // reserve 1 thread for task generation

        for (int i = 0; i < N; i++)
        {
            while (workingTasks >= workers)
            {
                #pragma omp taskyield
            }

            #pragma omp atomic update
                workingTasks++;

            #pragma omp task shared(configData, valueData, parallelSync, workingTasks) firstprivate(i)
            {
                loopFunc(configData, &valueData, &parallelSync, i);

                #pragma omp atomic update
                    workingTasks--;
            }
        }
        #pragma omp taskwait
    }
}
  1. A​​FAIK volatiles 不会阻止硬件重新排序,这就是为什么你 最终可能会导致内存混乱,因为数据尚未写入, 而标志已被消费线程视为 true
  2. 这就是为什么要提一点建议:改用 C11 原子以确保数据的可见性。如我所见,gcc 4.9 支持 c11 C11Status in GCC
  3. 您可以尝试将生成的任务分成 K 个任务组,其中 K == ThreadNum 并仅在任何 运行 个任务之后才开始生成后续任务(在第一组中的任务生成之后)完成了。因此,您有一个 不变性 ,即 每次您只有 K 个任务 运行 并安排在 K 个线程上
  4. 也可以通过使用 C11 中的原子标志来满足任务间依赖性。