OpenMP 手工还原指令

OpenMP Handmade reduction directive

本文关键字:指令 还原 OpenMP      更新时间:2023-10-16

我正在研究阶乘函数。我必须使用 OpenMP 编写其并行版本。

double sequentialFactorial(const int N) {
    double result = 1;
    for(int i = 1; i <= N; i++) {
        result *= i;
    }
    return result;
}

众所周知,该算法可以使用约简技术有效地并行化。

我知道存在reduction条款(标准§§2.15.3.6(。

double parallelAutomaticFactorial(const int N) {
    double result = 1;
    #pragma omp parallel for reduction(*:result)
    for (int i=1; i <= N; i++) 
        result *= i;
    return result;
}

但是,我想尝试实现"手工制作"的还原技术。

double parallelHandmadeFactorial(const int N) {
    // maximum number of threads
    const int N_THREADS = omp_get_max_threads();
    // table of partial results
    double* partial = new double[N_THREADS];
    for(int i = 0; i < N_THREADS; i++) {
        partial[i] = 1;
    }
    // reduction tecnique
    #pragma omp parallel for
    for(int i = 1; i <= N; i++) {
        int thread_index = omp_get_thread_num();
        partial[thread_index] *= i;
    }
    // fold results
    double result = 1;
    for(int i = 0; i < N_THREADS; i++) {
        result *= partial[i];
    }
    delete partial;
    return result;
}

我希望最后两个片段的性能非常相似,并且比第一个更好。但是,平均性能为:

Sequential Factorial          3500 ms
Parallel Handmade Factorial   6100 ms
Parallel Automatic Factorial   600 ms

我错过了什么吗?


多亏了 @Gilles 和 @P.W,这段代码可以按预期工作

double parallelNoWaitFactorial(const int N) {
    double result = 1;
    #pragma omp parallel
    {
        double my_local_result = 1;
        // removing nowait does not change the performance
        #pragma omp for nowait
        for(int i = 1; i <= N; i++)
            my_local_result *= i;
        #pragma omp atomic
        result *= my_local_result;
    }
    return result;
}

如果数组元素碰巧共享缓存行,则会导致错误共享,从而进一步导致性能下降。

要避免这种情况,请执行以下操作:

  • 使用私有变量double partial而不是double数组 partial .
  • 使用每个线程的partial结果计算关键区域中的最终result
  • 此最终result应为非并行区域专用的变量。

关键区域将如下所示:

#pragma omp critical
    result *= partial;