对OpenMP中静态调度开销的影响

Influence on the static scheduling overhead in OpenMP

本文关键字:影响 开销 静态调度 OpenMP      更新时间:2023-10-16

我考虑了哪些因素会影响OpenMP中的静态调度开销。在我看来,它受到以下因素的影响:

  • CPU性能
  • OpenMP运行库的具体实现
  • 线程数

但我是否遗漏了其他因素?也许是任务的规模…?

进一步:开销是否线性依赖于迭代次数?在这种情况下,我希望拥有静态调度和4核,开销随着4* I迭代线性增加。到目前为止正确吗?

编辑:我只对静态调度开销本身感兴趣。我不是在谈论线程启动开销、同步开销和临界区开销。

您需要将OpenMP创建线程组/线程池的开销与每个线程在for循环中操作单独的迭代器集的开销分开。

静态调度很容易手工实现(这有时非常有用)。让我们考虑我认为最重要的两个静态调度schedule(static)schedule(static,1),然后我们可以将其与schedule(dynamic,chunk)进行比较。
#pragma omp parallel for schedule(static)
for(int i=0; i<N; i++) foo(i);

等于(但不一定等于)

#pragma omp parallel
{
    int start = omp_get_thread_num()*N/omp_get_num_threads();
    int finish = (omp_get_thread_num()+1)*N/omp_get_num_threads();
    for(int i=start; i<finish; i++) foo(i);
}

#pragma omp parallel for schedule(static,1)
for(int i=0; i<N; i++) foo(i);

等价于

#pragma omp parallel 
{
    int ithread = omp_get_thread_num();
    int nthreads = omp_get_num_threads();
    for(int i=ithread; i<N; i+=nthreads) foo(i);
}

从这里你可以看到,实现静态调度非常简单,因此开销可以忽略不计。

另一方面,如果你想实现schedule(dynamic)(这是相同的schedule(dynamic,1))的手,它更复杂:

int cnt = 0;
#pragma omp parallel
for(int i=0;;) {
    #pragma omp atomic capture
    i = cnt++;
    if(i>=N) break;
    foo(i);                                    
}

这需要OpenMP>=3.1。如果您想在OpenMP 2.0(用于MSVC)中这样做,您需要使用像这样的关键字

int cnt = 0;
#pragma omp parallel
for(int i=0;;) {
    #pragma omp critical   
    i = cnt++;
    if(i>=N) break;
    foo(i);
} 

下面是等价于schedule(dynamic,chunk)(我没有使用原子访问对其进行优化):

int cnt = 0;
int chunk = 5;
#pragma omp parallel
{
    int start, finish;
    do {
        #pragma omp critical
        {
            start = cnt;
            finish = cnt+chunk < N ? cnt+chunk : N;
            cnt += chunk;
        }
        for(int i=start; i<finish; i++) foo(i);
    } while(finish<N);
}

显然,使用原子访问将导致更多的开销。这也说明了为什么为schedule(dynamic,chunk)使用更大的块可以减少开销。