代码之家  ›  专栏  ›  技术社区  ›  yardras

openmp 4.5任务的依赖项可变列表

  •  1
  • yardras  · 技术社区  · 8 年前

    我正在使用基于任务的范例编写fortran代码。 我使用DAG来表示依赖关系。 使用OpenMP 4.5,我可以使用 依赖 它将依赖类型和依赖列表作为输入。

    当您明确知道依赖项的数量时,这种机制工作得很好。 然而,在我的例子中,我会创建一些任务,这些任务应该有一个从1到n个元素不等的依赖关系列表。

    阅读文档 OpenMP-4.5_doc ,我没有找到任何有用的机制来提供可变的依赖项列表。

    让我们举个例子。 考虑交通量的计算。道路的从属关系是先前道路的计算状态(希望这足够清楚)。 因此,在计算所有先前道路交通量时,将执行此道路的计算。

    使用Fortran风格,我们有以下代码草图:

    !road is a structure such that
    ! type(road) :: road%dep(:)
    ! integer    :: traffic
    
    type(road) :: road
    
    !$omp task shared(road)
    !$omp depend(in: road%dep) depend(inout:road)
      call compute_traffic(road)
    !$omp end task
    

    我正在尝试使用字段%dep作为openmp的依赖项列表。 或者,我们可以考虑%dep具有不同的类型,作为指向相关道路的指针列表。


    除此之外,我还研究了稀疏直接解算器,并更精确地研究了Cholesky分解及其应用。使用多前沿方法,可以得到许多小的密集块。分解和求解分为两个子例程,第一个子例程是对角块的分解(或求解),第二个子例程是非对角块的更新。密集块的更新需要更新共享相同行的所有先前密集块。

    事实上,我的任务是更新一个可以依赖于多个块的非对角块,显然,依赖的数量与输入矩阵的模式(结构)有关。因此,不可能静态地确定依赖项的数量。这就是为什么我要在子句中列出一个块列表 依赖 .

    1 回复  |  直到 8 年前
        1
  •  1
  •   Cimbali    8 年前

    您正在查找的功能已在名称下提出 多重依赖关系 by Vidal et al. in the International Workshop on OpenMP, 2015 (参见 here for an open access version ).

    据我所知,这个功能还没有进入OpenMP任务中(到目前为止?),但你可以用 OmpSs ,这是OpenMP的先行者,也是该提案(以及更多)的实施者。

    否则,由于依赖项编号需要在编译时定义,最糟糕的解决方法是编写(或生成)一个 switch (或评级机构 SELECT CASE 对于Fortran),依赖项的数量,每个依赖项都有自己的单独pragma。

    恐怕我对Fortran不太了解,但在C语言中,使用X宏和 _Pragma() . 我认为GNU fortran使用的是C预处理器,所以希望您可以转置我曾经使用过的一些代码(否则您可能需要手工编写所有案例):

    // L(n, X) = Ln(X) is a list of n macro expansions of X
    #define L_EVALN(N, X) L ## N(X)
    #define L(N, X) L_EVALN(N, X)
    
    #define L1(X)         X(1,  b)
    #define L2(X)  L1(X)  X(2,  c)
    #define L3(X)  L2(X)  X(3,  d)
    #define L4(X)  L3(X)  X(4,  e)
    #define L5(X)  L4(X)  X(5,  f)
    #define L6(X)  L5(X)  X(6,  g)
    #define L7(X)  L6(X)  X(7,  h)
    #define L8(X)  L7(X)  X(8,  i)
    #define L9(X)  L8(X)  X(9,  j)
    #define L10(X) L9(X)  X(10, k)
    #define L11(X) L10(X) X(11, l)
    #define L12(X) L11(X) X(12, m)
    #define L13(X) L12(X) X(13, n)
    
    
    // Expand x, stringify, and put inside _Pragma()
    #define EVAL_PRAGMA(x) _Pragma (#x)
    #define DO_PRAGMA(x) EVAL_PRAGMA(x)
    
    // X-macro to define dependecies on b{id} (size n{id})
    #define OMP_DEPS(num, id) , [n_ ## id]b_ ## id
    
    // X-macro to define symbols b{id} n{id} for neighbour #num
    #define DEFINE_DEPS(num, id)               \
                double *b_ ## id =  b[num];    \
                int     n_ ## id = nb[num];
    
    // Calls each X-macros N times
    #define N_OMP_DEPS(N)       L(N, OMP_DEPS)
    #define N_CALL_DEPS(N)      L(N, CALL_DEPS)
    #define N_DEFINE_DEPS(N)    L(N, DEFINE_DEPS)
    
    // defines the base task with 1 dependency on b_a == *b,
    // to which we can add any number of supplementary dependencies
    #define OMP_TASK(EXTRA) DO_PRAGMA(omp task depend(in: [n_a]b_a EXTRA))
    
    // if there are N neighbours, define N deps and depend on them
    #define CASE(N, ...) case N:                                                     \
                    {                                                                \
                        N_DEFINE_DEPS(N)                                             \
                        OMP_TASK(N_OMP_DEPS(N))                                      \
                        {                                                            \
                            for (int i = 0; i < n; i++) b[i] = ... ;                 \
                        }                                                            \
                    } break;
    
    int task(int n, int *nb, double **b)
    {
        double *b_a = b[0];
        int nb_a = b[0];
        switch(n)
        {
            CASE(1)
            CASE(2)
            CASE(3)
            CASE(4)
        }
    }
    

    这将生成以下代码(如果对其进行修饰):

    int task(int n, int *nb, double **b)
    {
        double *b_a = b[0];
        int nb_a = b[0];
        switch (n)
        {
            case 1:
            {
                double *b_b = b[1];
                int n_b = nb[1];
                #pragma omp task depend(in: [n_a]b_a , [n_b]b_b)
                {
                    for (int i = 0; i < n; i++)
                        b[i] = ... ;
                }
            } break;
            case 2:
            {
                double *b_b = b[1];
                int n_b = nb[1];
                double *b_c = b[2];
                int n_c = nb[2];
                #pragma omp task depend(in: [n_a]b_a , [n_b]b_b , [n_c]b_c)
                {
                    for (int i = 0; i < n; i++)
                        b[i] = ... ;
                }
            } break;
            case 3:
            {
                double *b_b = b[1];
                int n_b = nb[1];
                double *b_c = b[2];
                int n_c = nb[2];
                double *b_d = b[3];
                int n_d = nb[3];
                #pragma omp task depend(in: [n_a]b_a , [n_b]b_b , [n_c]b_c , [n_d]b_d)
                {
                    for (int i = 0; i < n; i++)
                        b[i] = ... ;
                }
            } break;
            case 4:
            {
                double *b_b = b[1];
                int n_b = nb[1];
                double *b_c = b[2];
                int n_c = nb[2];
                double *b_d = b[3];
                int n_d = nb[3];
                double *b_e = b[4];
                int n_e = nb[4];
                #pragma omp task depend(in: [n_a]b_a , [n_b]b_b , [n_c]b_c , [n_d]b_d , [n_e]b_e)
                {
                    for (int i = 0; i < n; i++)
                        b[i] = ... ;
                }
            } break;
        }
    }
    

    虽然这很可怕,但这是一个解决办法,它的主要好处是:它可以工作。

    推荐文章