如何使用并行化两个串行 for 循环,使两个 for 循环的工作分布在线程上

how to use parallelize two serial for loops such that the work of the two for loops are distributed over the thread

我编写了以下代码来并行化两个 'for' 循环。

#include <iostream>
#include <omp.h>
#define SIZE 100

    int main()
    {
        int arr[SIZE];
        int sum = 0;    
        int i, tid, numt, prod;
        double t1, t2;
        for (i = 0; i < SIZE; i++)
            arr[i] = 0;     
    
        t1 = omp_get_wtime();   
    
    #pragma omp parallel private(tid, prod)
        {       
            tid = omp_get_thread_num();
            numt = omp_get_num_threads();
            std::cout << "Tid: " << tid << " Thread: " << numt << std::endl;
    #pragma omp for reduction(+: sum) 
            for (i = 0; i < 50; i++) {
                prod = arr[i]+1;
                sum += prod;
            }
                
    #pragma omp for reduction(+: sum) 
            for (i = 50; i < SIZE; i++) {
                prod = arr[i]+1;
                sum += prod;
            }                                   
    
        }
    
        t2 = omp_get_wtime();
        std::cout << "Time taken: " << (t2 - t1) << ", Parallel sum: " << sum << std::endl;
    
        return 0;
    }

在这种情况下,第一个 'for' 循环的执行由所有线程并行完成,结果累积在 sum 变量中。第一个 'for' 循环执行完成后,线程开始并行执行第二个 'for' 循环,结果累积在 sum 变量中。在这种情况下,显然第二个 'for' 循环的执行等待第一个 'for' 循环的执行结束。

我想通过线程同时处理两个 'for' 循环。我怎样才能做到这一点?有没有其他方法可以更有效地编写此代码。忽略我在 'for' 循环中所做的虚拟工作。

您可以声明循环 nowait 并将缩减移动到并行部分的末尾。像这样:

#   pragma omp parallel private(tid, prod) reduction(+: sum) 
    {       
#           pragma omp for nowait 
            for (i = 0; i < 50; i++) {
                prod = arr[i]+1;
                sum += prod;
            }    
#           pragma omp for nowait 
            for (i = 50; i < SIZE; i++) {
                prod = arr[i]+1;
                sum += prod;
            }
    }

如果您使用 #pragma omp for nowait 所有线程都分配给第一个循环,只有当至少一个线程在第一个循环中完成时,第二个循环才会开始。不幸的是,没有办法告诉 omp for 构造使用例如只有一半的线程。

幸运的是,有一个解决方案可以通过使用任务来做到这一点(即 运行 2 个循环并行)。下面的代码将一半的线程用于 运行 第一个循环,另一半用于 运行 第二个循环,使用 taskloop 构造和 num_threads 子句来控制线程分配给一个循环。这将完全按照您的预期进行,但您必须测试哪种解​​决方案在您的情况下更快。

#pragma omp parallel
#pragma omp single
{       
    int n=omp_get_num_threads();
    #pragma omp taskloop num_tasks(n/2)
       for (int i = 0; i < 50; i++) {
                //do something
       }    
    #pragma omp taskloop num_tasks(n/2)
       for (int i = 50; i < SIZE; i++) {
                //do something
       }
}

更新:第一段并不完全正确,通过更改 chunk_size,您可以控制第一个循环中使用的线程数。它可以通过使用例如来完成schedule(linear, chunk_size) 子句。所以,我认为设置 chunk_size 就可以了:

#pragma omp parallel
{       
    int n=omp_get_num_threads();

    #pragma omp single
    printf("num_threads=%d\n",n);

    #pragma omp for schedule(static,2) nowait
       for (int i = 0; i < 4; i++) {
                printf("thread %d running 1st loop\n", omp_get_thread_num());
        }    
    #pragma omp for schedule(static,2) 
        for (int i = 4; i < SIZE; i++) {
                printf("thread %d running 2nd loop\n", omp_get_thread_num());
        }
}

但起初结果似乎令人惊讶:

num_threads=4
thread 0 running 1st loop
thread 0 running 1st loop
thread 0 running 2nd loop
thread 0 running 2nd loop
thread 1 running 1st loop
thread 1 running 1st loop
thread 1 running 2nd loop
thread 1 running 2nd loop

这是怎么回事?为什么不使用线程 2 和 3? OpenMP run-time 保证如果您有两个具有相同迭代次数的独立循环并使用静态调度以相同数量的线程执行它们,那么每个线程将在两个并行区域中接收完全相同的迭代范围。 另一方面,使用 schedule(dynamic,2) 子句的结果非常令人惊讶 - 只使用了一个线程,CodeExplorer link 是 here.