openmp 4.5任务的变量依赖列表

时间:2018-02-21 16:52:43

标签: fortran task scheduled-tasks openmp

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

当您明确知道依赖项的数量时,此机制很有效。 但是,在我的情况下,我将创建预期具有从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 个答案:

答案 0 :(得分:1)

您正在寻找的功能名称为多重依赖 by Vidal et al. in the International Workshop on OpenMP, 2015(请参阅here for an open access version)。

据我所知,这个功能还没有进入OpenMP任务(但是?),但你可以使用OmpSs,这个提议(以及更多)实现的OpenMP先行者。

丑陋的解决方法,否则,因为您的依赖项编号需要在编译时定义,就是在依赖项的数量上编写(或生成)switch(或者为Fortran的rater SELECT CASE),每个都有自己独立的编译指示。

我不太了解Fortran我很害怕,但在C语言中你可以通过X-macros和_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;
    }
}

尽管这是可怕的,但它是一种解决方法,它的主要优点是:它有效。