首页/文章/ 详情

Fluent 并行UDF丨03 代码并行化

精品
作者优秀平台推荐
详细信息
文章亮点
作者优秀
优秀教师/意见领袖/博士学历/特邀专家
平台推荐
内容稀缺
8月前浏览8945

本文摘要(由AI生成):

本文主要介绍了Fluent UDF串行代码并行化处理的问题,包括编译器指令的使用、DPM模型UDF的并行化、Host与Node节点通信以及全局约简宏的使用。编译器指令可以帮助用户将UDF代码分别指定给Host和Node节点运行,DPM模型UDF可以使用共享内存和消息传递两种方式进行并行化处理,Host与Node节点通信可以使用host_to_node和node_to_host宏进行数据传递,全局约简宏可以用于从所有计算节点收集数据并进行约简。


本文简单介绍Fluent UDF串行代码并行化处理的问题。

注:以下内容来自Fluent UDF文档。

1 介绍

Fluent求解器包含三种类型的执行器:Cortexhost以及node。当Fluent运行时,启动1个Cortex实例,之后启动1个host及n个nodes,因此总共有有n+2个运行进程。因此,在并行计算(串行计算也推荐)时,用户需确保UDF成功地在host及node节点上运行。首先,可能需要用户准备两个不同的UDF版本:一个用于host,另一个用于node。不过最好的做法是编写一个UDF,使其编译后可以在不同的版本上运行,这个过程称之为串行UDF的并行化处理。

用户可以向UDF中添加特殊的宏和编译器指令来实现这一过程。

编译器指令(例如#if RP_NODE、RP_HOST)及其否定形式,能够指定编译器只编译UDF中应用于特定处理器的代码,而忽略其余部分,关于编译器指令的使用,我们在后面再详细描述。

如果串行UDF执行一个依赖于其他计算节点(或主机)发送或接收数据的操作,或者使用Fluent 18.2之后版本引入的宏类型,那么该串行UDF必须是能够并行的。还有一些操作必须将串行代码并行化:

  • 文件读写
  • Global Reductions
  • 全局求和
  • 全局求最小及最大值
  • 全局求逻辑值
  • 一些包含网格及网格面的循环
  • 在console窗口显示消息
  • 向Host或Node节点打印信息

当串行代码实现并行化修改后,即可采用与串行代码相同的方式进行编译及挂载。

2 DPM模型UDF的并行化

DPM模型可以使用以下下两种并行方式:

  • 共享内存(Shared Memory)
  • 消息传递(Message Passing)

当用户使用DPM相关的UDF宏时,其必须以上面该两种并行方式中的其中一种运行。由于DPM模型所需的所有流体变量保存在被跟踪的颗粒的数据结构中,因此在并行Fluent中使用DPM UDF时无需特别注意。不过以下两种情况例外:

  • 当使用DEFINE_DPM_OUTPUT宏输出颗粒信息时,不允许使用c函数frpintf,此时应当使用特定的函数par_fprintfpar_fprintf_head采用并行方式写入文件。每个计算节点将颗粒信息写入到一个单独的临时文件,之后由Fluent排序后输出到最终文件。特定的输出函数与c函数fprintf采用相同的参数列表,但在当Fluent需要对文件进行排序时,需要指定一个扩展的参数列表。
  • 当存储颗粒信息时,并行模拟时需要使用特定的变量,这些变量可以通过宏TP_USER_REAL(tp, i) (tp是类型Tracked_Particle *)和PP_USER_REAL(p, i) (p是类型Particle *)访问。只有这些这样颗粒信息才能跨越分区边界,而其他局部或全局变量无法跨越分区边界。

需要注意,如果想要访问其他数据(如单元网格上的物理量),那么除了共享内存的并行方式外,用户可以访问所有流动和求解变量,但是如果采用了共享内存的方式,则只能访问宏SV_DPM_LIST和SV_DPMS_LIST中定义的变量。这些宏在dpm.h文件中定义。

本节包含可以用来并行化串行UDF的宏。可以在引用的头文件(例如para.h)中找到这些宏的定义。

3 编译器指令

在将UDF转化为并行时,代码中的某些部分可能需要由Host节点完成,而另一部分则可能需要由Node节点完成。通过使用Fluent提供的编译器指令,可以分别指定代码哪些部分由Host或Node节点运行。用户为Host和Node节点编写一个UDF源文件,但是编译后可以生成不同的动态链接库版本。如用户可以将打印任务分配给Host节点,将任务计算整个区域网格的总体积分配给node节点。由于大多数操作系统是由host或node节点执行的,因此通常采用否定形式的编译器指令。

需要注意,Host节点的主要目的是解释来自Cortex的命令或数据,并将命令或数据传递给node-0节点。由于Host节点中不包含网格数据,因此需要格外小心不要在任何计算中Host节点,以防出现分母为零的情况。在这种情况下,需要将这些操作包裹在#if !RP_HOST指令中,以指示编译器在执行与网格相关的计算时忽略Host节点。如想要利用UDF计算一个面Thread上的总面积,之后利用该总面积计算物理量的通量,如果不将Host排除在操作之外,则Host节点就是得到 的总面积为零,当UDF试图除以零计算通量时,将会出现浮点异常的错误提示。

代码示例:

#if !RP_HOST
   avg_pres = total_pres_a / total_area;
#endif

上面代码指定了求商操作在node节点上完成。

当需要从没有数据的操作中排除node节点时,可以使用#if !RP_NODE指令。

下面是一个并行编译器指令的列表,以及它们的作用

/*************************/
/*  Compiler Directives */
/***********************/

#if RP_HOST
   /* 只在Host节点中处理*/
#endif

#if RP_NODE
   /* 只在Node节点中处理*/
#endif

#if !RP_HOST
   /* 只在Node节点中处理*/
#endif

#if !RP_NODE
   /*只在Host节点中处理*/
#endif

下面UDF简单展示了编译器指令的使用。DEFINE_ADJUST宏中定义了一个名为where_am_i的函数。此函数查询以确定正在执行哪种类型的进程,然后在计算的节点上显示一条消息。

/*****************************************************
 Simple UDF that uses compiler directives
*****************************************************/
#include "udf.h"
DEFINE_ADJUST(where_am_i, domain)
{
#if RP_HOST
   Message("I am in the host process\n");
#endif /* RP_HOST */

#if RP_NODE
   Message("I am in the node process with ID %d\n",myid);
#endif
}

这种不同类型处理器之间的简单功能分配在实际情况下是有用的。例如,用户可能希望在运行特定计算时(通过使用RP_NODE或!RP_HOST)在计算节点上显示一条消息。或者用户也可以选择指定host进程来显示消息(通过使用RP_HOST或!RP_NODE)。通常,用户希望主机进程只写一次消息。或者用户可能希望从所有nodes收集数据,并从host打印一次总数。要执行这种类型的操作,UDF需要在进程之间进行某种形式的通信。最常见的通信模式是host和node进程之间的通信。

4 Host与Node节点通信

Fluent提供了两个宏用于Host与Node节点之间的数据通信:host_to_node_type_numnode_to_host_type_num

4.1 Host-to-Node数据传递

从Host节点向所有node节点发送数据,可以使用宏host_to_node_type_num。该宏的表达形式为:

host_to_node_type_num(val_1, val_2,...,val_num);

其中num是将在参数列表中传递的变量的数量,type是将传递的变量的数据类型。可以传递的变量的最大数量是7。数组和字符串也可以一次一个地从主机传递到节点,如下面的示例所示。

/* integer and real variables passed from host to nodes */
host_to_node_int_1(count);
host_to_node_real_7(len1, len2, width1, width2, breadth1, breadth2, vol);

/* string and array variables passed from host to nodes */
char wall_name[]="wall-17";
int thread_ids[10] = {1,29,5,32,18,2,55,21,72,14};

host_to_node_string(wall_name,8); /* remember terminating NUL character */
host_to_node_int(thread_ids,10);

注意,这些host_to_node通信宏不需要受并行udf的编译器指令保护,因为所有这些宏都会自动执行以下操作:

  • 如果编译为Host版本,则发送数据
  • 若编译为node版本,则接收数据

这组宏最常见的用途是将参数或边界条件从Host传递给Node节点。

4.2 Node-to-Host数据传递

从node-0节点向Host节点发送数据,可以使用宏:

node_to_host_type_num(val_1,val_2,...,val_num);

其中num是将在参数列表中传递的变量的数量,type是将传递的变量的数据类型。可以传递最多7个变量。如果想要传递更多的变量,可以使用数组。数组和字符串可以一次一个地从主机传递到节点,如下面的示例所示。

/* integer and real variables passed from compute node-0 to host */
node_to_host_int_1(count);
node_to_host_real_7(len1, len2, width1, width2, breadth1, breadth2, vol);

/* string and array variables passed from compute node-0 to host */
char *string;
int string_length;
real vel[ND_ND];

node_to_host_string(string,string_length);
node_to_host_real(vel,ND_ND);

host_to_node宏是host节点将数据传递给所有的node节点(通过node-0节点间接传递),而node_to_host宏只是node-0节点向host节点传递数据。

node_to_host宏不需要编译器指令(例如#if RP_NODE)的保护,因为它们会自动执行以下操作

  • 如果节点是node-0,并且将UDF编译为node版本,则发送数据
  • 如果UDF编译为node版本,但节点不是-node0,则什么事情都不做
  • 如果UDF编译为host版本,则接收数据

这组宏最常见的用法是将node-0结果传递给host。

5 逻辑判断

在并行Fluent中有许多可以扩展为逻辑测试的宏。这些逻辑宏称为判断式,由后缀P表示,可以用作UDF中的测试条件。如果满足括号中的条件,以下判断式将返回TRUE。

# define MULTIPLE_COMPUTE_NODE_P (compute_node_count > 1)
# define ONE_COMPUTE_NODE_P (compute_node_count == 1)
# define ZERO_COMPUTE_NODE_P (compute_node_count == 0)

有许多判断式允许使用计算节点ID来测试UDF中node节点标识。计算节点的ID存储为全局整数变量myid。下面列出的每个宏都可以用来测试进程myid的某些条件。例如,判断式I_AM_NODE_ZERO_P将myid的值与node-0的ID进行比较,并在两者相同时返回TRUE。另一方面,I_AM_NODE_SAME_P(n)比较在n中传递的计算节点ID和myid。当两个id相同时,函数返回TRUE。节点ID判断式通常用于udf中的条件-if语句。

/* predicate definitions from para.h header file */

# define I_AM_NODE_HOST_P (myid == host)
# define I_AM_NODE_ZERO_P (myid == node_zero)
# define I_AM_NODE_ONE_P (myid == node_one)
# define I_AM_NODE_LAST_P (myid == node_last)
# define I_AM_NODE_SAME_P(n) (myid == (n))
# define I_AM_NODE_LESS_P(n) (myid < (n))
# define I_AM_NODE_MORE_P(n) (myid > (n))

在一个分区网格中,一个面可能同时出现在一个或两个分区中,为了使求和操作不重复计算它,它只被正式分配给一个分区。上面的判断式与相邻网格的分区ID一起使用,以确定它是否属于当前分区。使用的约定是将编号较小的计算节点指定为该面的principal计算节点。如果面位于其principal计算节点上,则PRINCIPAL_FACE_P返回TRUE。当希望对面执行全局且其中一些面是分区边界面时,可以使用该宏作为测试条件。下面是来自para.h的PRINCIPAL_FACE_P的定义。

/* predicate definitions from para.h header file */
# define PRINCIPAL_FACE_P(f,t) (!TWO_CELL_FACE_P(f,t) || \
 PRINCIPAL_TWO_CELL_FACE_P(f,t))

# define PRINCIPAL_TWO_CELL_FACE_P(f,t) \
 (!(I_AM_NODE_MORE_P(C_PART(F_C0(f,t),THREAD_T0(t))) || \
 I_AM_NODE_MORE_P(C_PART(F_C1(f,t),THREAD_T1(t)))))

6 全局约简宏

全局约简(global reduction)操作是从所有计算节点收集数据,并将数据约简为单个值或数组的操作。这些操作包括全局求和、全局最大值和最小值以及全局逻辑判断等。这些宏以前缀PRF_G开头,在头文件prf.h中定义。全局求和宏用后缀SUM表示、全局最大值用HIGH表示,全局最小值用LOW表示。后缀AND和OR标识全局逻辑。

变量数据类型在宏名称中标识,其中R表示实际数据类型,I表示整数,L表示逻辑。例如,宏PRF_GISUM查找计算节点上整数的总和。

每个全局约简宏都有两个不同的版本:一个采用单个变量参数,另一个采用变量数组。

  • 宏名称中带有后缀1的宏接受一个参数,并返回单个值作为全局约简结果。例如,宏PRF_GIHIGH1(x)接受一个参数x并在所有计算节点中计算变量x的最大值,然后返回该值。,如下面的示例所示。
{
    int y;
    int x = myid;
    y = PRF_GIHIGH1(x);
}
  • 没有1后缀的宏计算全局约简变量数组。这些宏有三个参数:x、N和iwork,其中x是一个数组,N是数组中元素数量,iwork是一个与临时存储所需的x类型和大小相同的数组。这种类型的宏被传递给一个数组x,数组x的元素在从函数返回后被新的结果填充。例如,宏PRF_GIHIGH(x,N,iwork)计算x数组中每个元素在所有计算节点上的最大值,使用数组iwork作为临时存储,并通过将每个元素替换为结果的全局最大值来修改数组x。该函数不返回值。
{
   real x[N], iwork[N];
   PRF_GRHIGH(x,N,iwork);
}

6.1 全局求和

可用于计算变量的全局和的宏由后缀SUM标识。宏PRF_GISUM1及PRF_GISUM分别计算整数变量和整数变量数组的全局和。

PRF_GRSUM1(x)跨所有计算节点计算实变量x的和。运行单精度版本的Fluent时,全局和为浮点型,运行双精度版本时,全局和为双精度型。另外,PRF_GRSUM(x,N,iwork)在双精度时返回浮点数组,单精度返回double数组。

宏形式宏描述
PRF_GISUM1(x)返回所有计算节点上整型变量x的和
PRF_GISUM(x,N,iwork)设置数组x存储所有计算节点上的和
PRF_GRSUM1(x)返回所有计算节点上的变量x的和,单精度返回float,双精度返回double
PRF_GRSUM(x,N,iwork)设置x为包含所有计算节点上变量的和的数组,单精度返回float数组,双精度返回double数组

注:数组调用为传址调用。

6.2 全局最大最小值

与全局求和类似,后缀为HIGH及LO的宏用于计算全局的最大值与最小值。

宏形式宏描述
PRF_GHIGH1(x)返回所有计算节点上整型变量x的最大值
PRF_GHIGH(x,N,iwork)设置x为包含所有计算节点上最大值的数组
PRF_GRHIGH1(x)返回所有计算节点上的变量x的最大值,单精度返回float,双精度返回double
PRF_GRHIGH(x,N,iwork)设置x为包含所有计算节点上变量最大值的数组,单精度返回float数组,双精度返回double数组
PRF_GILOW1(x)设置x为包含所有计算节点上最小值的数组
PRF_GILOW(x,N,iwork)设置x为包含所有计算节点上最小值的数组
PRF_GRLOW1(x)返回所有计算节点上的变量x的最小值,单精度返回float,双精度返回double
PRF_GRLOW(x,N,iwork)设置x为包含所有计算节点上变量最小值的数组,单精度返回float数组,双精度返回double数组

6.3 全局逻辑值

后缀AND及OR的宏可用于计算全局逻辑与及逻辑或的值。宏PRF_GLOR1(x)可以跨所有计算节点计算变量x的全局逻辑或。PRF_GLOR(x,N,iwork)计算变量数组x的全局逻辑或。如果计算节点上的任何对应元素为TRUE,则将x的元素设置为TRUE。

宏形式宏描述
PRF_GLOR1(x)任何计算节点值为TRUE则返回TRUE
PRF_GLOR1(x,N,work)任何元素为TRUE则返回TRUE
PRF_GLAND1(x)所有计算节点x值为TRUE则返回TRUE
PRF_GLAND(x)所有变量数组元素为TRUE则返回TRUE

6.4 全局同步

如果希望在执行下一个操作之前全局同步计算节点,可以使用PRF_GSYNC()。当在UDF中插入PRF_GSYNC宏时,在源代码中的上述命令在所有计算节点上完成之前,不会执行任何其他命令。在调试函数时,同步可能也很有用。


Fluent
著作权归作者所有,欢迎分享,未经许可,不得转载
首次发布时间:2020-03-26
最近编辑:8月前
CFD之道
博士 | 教师 探讨CFD职场生活,闲谈CFD里外
获赞 2578粉丝 11415文章 744课程 27
点赞
收藏
作者推荐
未登录
还没有评论
课程
培训
服务
行家
VIP会员 学习计划 福利任务
下载APP
联系我们
帮助与反馈