【问题标题】:how to find the tradeoff between serial and parallel execution of for loop in C/C++?如何找到 C/C++ 中 for 循环的串行和并行执行之间的权衡?
【发布时间】:2021-08-18 11:08:36
【问题描述】:

我有一个相当大的应用程序,其中包含大量 OpenMP 并行循环,我使用 #pragma omp parallel for 使循环并行运行。然而,我注意到运行具有小迭代的循环可能不值得并行运行。因此,我决定使用 OpenMP if 子句来决定串行执行还是并行执行。

另一方面,每个循环的迭代成本可能取决于程序输入和循环计算数据类型(例如模板函数)。换句话说,我似乎需要一种方法,能够确定是否值得在运行时预先并行化一个循环。

请告诉我什么是 C++ 工具,以便更好地决定何时并行或串行运行任意循环,因为

  1. 循环计数仅在运行时知道

  2. 循环计算类型可以是模板变量。

  3. 应用程序可用的线程数仅在应用程序执行开始时设置。

非常感谢您宝贵的 cmets。

【问题讨论】:

  • 分析,然后您可以将并行化相应地限制为某个变量 if(loop is enough big) 然后#pragma omp parallel for。看看stackoverflow.com/a/41450827/1366871
  • 是的,但是如果它是一个模板函数,其中计算类型(浮点数,双精度)在编译时间之前是未知的,并且循环行程计数在运行之前是未知的,特别是如果循环计数是 / 的函数,取决于应用程序的输入。我想没有办法,除非如您所说,我们针对典型应用程序的输入分析应用程序,然后根据分析结果(循环的典型行程计数/计算类型)我们可以决定在 if 子句中放入什么阈值。
  • 另一种方法可能是提出运行时启发式方法来衡量在应用程序开始时创建并行区域的成本,然后对于我们在运行时遇到的每个循环,我们将该成本转换为循环迭代的数量,并基于该数量,我们可以决定是否值得为该特定循环设置一个并行区域。请让我知道您对此的想法...问候
  • 这看起来是个好主意,另一个可能的想法是在模板中你也可以添加一个权重,这个权重基本上可以量化计算的计算要求,结合循环计数应该给出你估计好。您需要小心确保启发式方法本身不会增加高开销。我不是 C++ 专家,所以我不确定这种方法到底有多可行
  • 您可以使用#pragma omp parallel for if (...),其中条件是运行时条件。所以你可以在运行时决定是否并行执行一个区域。

标签: c++ multithreading gcc openmp


【解决方案1】:

我想我设法获得了一个循环可以执行的最大迭代次数的下限,而串行执行优于并行执行。但是,我希望您对此发表评论,让我们看看是否有更好的方法来找到循环体迭代的正确折衷/估计次数,小于从并行 for 区域中受益的没有用处。 (非常感谢

让我们说:

N:是并行区域中运行循环的线程数。

R:是打开和关闭并行区域的成本(为了简单起见,以循环迭代次数而不是纳秒来衡量。)

S:是最大迭代次数(权衡),大于串行执行所需的时间与并行执行(包括并行区域开销)一样多。

所以基本上我们正在寻找 S 使得:并行执行时间 = 串行执行时间。这将是:R + S/N = S。

当求解 S 时:S = R x N / (N-1)。所以我用下面的代码测试了这个想法:

我在哪里执行以下步骤:

  1. 第 78-96 行:测量创建/分叉和最终确定/加入并行区域的成本。
  2. 第 99-111 行:测量循环的一次迭代的成本。
  3. 第 113-116 行:使用上面讨论的公式获得权衡。
  4. 第 119 行:我运行测试只是为了比较并行执行与串行执行。

#include <iostream>
#include <omp.h>
// #include <ctime>
#include <chrono>

// #define dtype float
// #define NROWS 1350 // float
#define dtype double
#define NUMEL 1000 // double

#define NPAR_REGION 100000
#define TEST_REP 1000
#define NREP 1000

#define printvar(x) std::cout << #x << " = " << x << std::endl;
#define printline std::cout << "LINE: " << __LINE__ << std::endl;

// #pragma omp declare simd
template <typename T>
T loop_body_func(T x, T y){
    return sqrt(x*sin(x)/(1+y));
}

using namespace std;


int compare_seri_vs_para(int numel, int nrep){  
    dtype *m = (dtype*) malloc(sizeof(dtype)*numel) ;
    dtype *n = (dtype*) malloc(sizeof(dtype)*numel) ;
    dtype *mn = (dtype*) malloc(sizeof(dtype)*numel) ;

    int a;
    chrono::steady_clock::time_point st = chrono::steady_clock::now();
    #pragma noparallel
    for(int k = 0; k < nrep; k++){
        #pragma omp parallel for if (true)
        #pragma novector
        for(int i = 0; i < numel; i++){
            mn[i] = loop_body_func(m[i], n[i]);
            // if (i % 100 == 0){
            //  printvar(omp_get_thread_num());
            // }
        }
    }
    chrono::steady_clock::time_point en = chrono::steady_clock::now();
    std::cout << "parallel ellapsed time: \t" <<
        chrono::duration_cast<chrono::nanoseconds>(en - st).count() << std::endl;
    st = chrono::steady_clock::now();
    #pragma noparallel
    for(int k = 0; k < nrep; k++){
        #pragma noparallel
        #pragma novector
        for(int i = 0; i < numel; i++){
            mn[i] = loop_body_func(m[i], n[i]);
            // if (i % 100 == 0){
            //  printvar(omp_get_thread_num());
            // }
        }

    }
    en = chrono::steady_clock::now();
    std::cout << "serial ellapsed time: \t\t" <<  
        chrono::duration_cast<chrono::nanoseconds>(en - st).count() << std::endl;

    free(m); free(n); free(mn); 
    return 0;
}


int main(){
    printline
    int nthread = omp_get_max_threads ();
    printvar(nthread)
    // chrono::steady_clock::time_point  *thread_cost = 
    //  (chrono::steady_clock::time_point  *) calloc(nthread, sizeof(chrono::steady_clock::time_point));
    double *thread_cost = (double*) calloc(nthread, sizeof(double));
    chrono::steady_clock::time_point  sc = chrono::steady_clock::now();
    #pragma noparallel
    #pragma novector
    for(int i = 0; i < NPAR_REGION; i++){
        #pragma omp parallel
        {
            int tid = omp_get_thread_num();
            thread_cost[tid] += tid; // a dummy task        
            // chrono::duration_cast<chrono::nanoseconds>(chrono::steady_clock::now() - sc).count()
        }
    }

    printline
    double paralell_region_cost = chrono::duration_cast<chrono::nanoseconds>(chrono::steady_clock::now() - sc).count();
    // for(int i = 0; i < nthread; i++) 
    //  paralell_region_cost+= thread_cost[i];
    paralell_region_cost /= NPAR_REGION;

    printvar(paralell_region_cost)
    printline

    dtype *m = (dtype*) malloc(sizeof(dtype)*TEST_REP) ;
    dtype *n = (dtype*) malloc(sizeof(dtype)*TEST_REP) ;
    dtype *mn = (dtype*) malloc(sizeof(dtype)*TEST_REP) ;

    sc = chrono::steady_clock::now();
    #pragma noparallel
    #pragma novector
    for(int i = 0; i < TEST_REP; i++){
        mn[i] = loop_body_func(m[i], n[i]);
    }
    double loop_body_cost = \
            chrono::duration_cast<chrono::nanoseconds>(chrono::steady_clock::now() - sc).count()/TEST_REP;
    printvar(loop_body_cost)

    printline
    int loop_parallel_serial_tradeoff = (paralell_region_cost / loop_body_cost)*(nthread/(double(nthread)-1.0));
    printline
    printvar(loop_parallel_serial_tradeoff)

    printline
    compare_seri_vs_para(loop_parallel_serial_tradeoff, NREP);

    free(m); free(n); free(mn); 
    printvar("-=program ended!=-")
}

我使用以下命令行编译了我的代码 sn-p:

icl.exe -Qopt-report:5 -Qopt-report-phase:all /Ob0  -Qopenmp -Qsimd -Qopenmp-simd  -arch:avx2  -Qdiag-error-limit:5   -c omp_if_test.cpp -Fo:omp_if_test.obj
Intel(R) C++ Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.1.2.254 Build 20200623
Copyright (C) 1985-2020 Intel Corporation.  All rights reserved.

icl: remark #10397: optimization reports are generated in *.optrpt files in the output location
omp_if_test.cpp
xilink.exe omp_if_test.obj -LIBPATH:../../Debug/lib -out:omp_if_test.exe
xilink: executing 'link'
Microsoft (R) Incremental Linker Version 14.28.29913.0
Copyright (C) Microsoft Corporation.  All rights reserved.

omp_if_test.obj
-LIBPATH:../../Debug/lib
-out:omp_if_test.exe

最后,这里的输出有点奇怪,因为串行执行似乎总是优于并行执行几乎 3 倍。 (lol) (我期待一个波动的情况)。

C:\simdtests>omp_if_test.exe
LINE: 72
nthread = 8
LINE: 90
paralell_region_cost = 2053.75
LINE: 97
loop_body_cost = 32
LINE: 113
LINE: 115
loop_parallel_serial_tradeoff = 73
LINE: 118
parallel ellapsed time:         3392300
serial ellapsed time:           1141000
"-=program ended!=-" = -=program ended!=-

C:\simdtests>omp_if_test.exe
LINE: 72
nthread = 8
LINE: 90
paralell_region_cost = 2304
LINE: 97
loop_body_cost = 33
LINE: 113
LINE: 115
loop_parallel_serial_tradeoff = 79
LINE: 118
parallel ellapsed time:         3275000
serial ellapsed time:           1216000
"-=program ended!=-" = -=program ended!=-

C:\simdtests>omp_if_test.exe
LINE: 72
nthread = 8
LINE: 90
paralell_region_cost = 2132.9
LINE: 97
loop_body_cost = 32
LINE: 113
LINE: 115
loop_parallel_serial_tradeoff = 76
LINE: 118
parallel ellapsed time:         3337900
serial ellapsed time:           1123100
"-=program ended!=-" = -=program ended!=-

C:\simdtests>omp_if_test.exe
LINE: 72
nthread = 8
LINE: 90
paralell_region_cost = 2062.77
LINE: 97
loop_body_cost = 32
LINE: 113
LINE: 115
loop_parallel_serial_tradeoff = 73
LINE: 118
parallel ellapsed time:         3739700
serial ellapsed time:           1118400
"-=program ended!=-" = -=program ended!=-

C:\simdtests>omp_if_test.exe
LINE: 72
nthread = 8
LINE: 90
paralell_region_cost = 2121.74
LINE: 97
loop_body_cost = 91
LINE: 113
LINE: 115
loop_parallel_serial_tradeoff = 26
LINE: 118
parallel ellapsed time:         5627200
serial ellapsed time:           356300
"-=program ended!=-" = -=program ended!=-

所以看起来我获得了权衡值的下限,而不是实际值。

如果您能对我们发表评论,我们将不胜感激。

问候

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-07
    • 1970-01-01
    • 2017-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-24
    • 1970-01-01
    相关资源
    最近更新 更多