我正在使用基于任务的范例编写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分解及其应用。使用多正面方法,您可以获得许多小密集块。因子分解和求解被分成两个子程序,首先是对角线块的分解(或求解),第二是对角线块的更新。密集块的更新需要更新共享相同行的所有先前密集块。
事实是,我有一项任务是更新一个可以依赖于多个块的非对角线块,显然,依赖性的数量与输入矩阵的模式(结构)有关。因此,不可能静态地确定依赖性的数量。这就是为什么我试图在依赖条款中给出一个块列表。
答案 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;
}
}
尽管这是可怕的,但它是一种解决方法,它的主要优点是:它有效。