本文摘要(由AI生成):
本文主要介绍了Fluent UDF串行代码并行化处理的问题,包括编译器指令的使用、DPM模型UDF的并行化、Host与Node节点通信以及全局约简宏的使用。编译器指令可以帮助用户将UDF代码分别指定给Host和Node节点运行,DPM模型UDF可以使用共享内存和消息传递两种方式进行并行化处理,Host与Node节点通信可以使用host_to_node和node_to_host宏进行数据传递,全局约简宏可以用于从所有计算节点收集数据并进行约简。
本文简单介绍Fluent UDF串行代码并行化处理的问题。
注:以下内容来自Fluent UDF文档。
Fluent求解器包含三种类型的执行器:Cortex、host以及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必须是能够并行的。还有一些操作必须将串行代码并行化:
当串行代码实现并行化修改后,即可采用与串行代码相同的方式进行编译及挂载。
DPM模型可以使用以下下两种并行方式:
当用户使用DPM相关的UDF宏时,其必须以上面该两种并行方式中的其中一种运行。由于DPM模型所需的所有流体变量保存在被跟踪的颗粒的数据结构中,因此在并行Fluent中使用DPM UDF时无需特别注意。不过以下两种情况例外:
需要注意,如果想要访问其他数据(如单元网格上的物理量),那么除了共享内存的并行方式外,用户可以访问所有流动和求解变量,但是如果采用了共享内存的方式,则只能访问宏SV_DPM_LIST和SV_DPMS_LIST中定义的变量。这些宏在dpm.h文件中定义。
本节包含可以用来并行化串行UDF的宏。可以在引用的头文件(例如para.h)中找到这些宏的定义。
在将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进程之间的通信。
Fluent提供了两个宏用于Host与Node节点之间的数据通信:host_to_node_type_num及node_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节点。
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结果传递给host。
在并行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)))))
全局约简(global reduction)操作是从所有计算节点收集数据,并将数据约简为单个值或数组的操作。这些操作包括全局求和、全局最大值和最小值以及全局逻辑判断等。这些宏以前缀PRF_G开头,在头文件prf.h中定义。全局求和宏用后缀SUM表示、全局最大值用HIGH表示,全局最小值用LOW表示。后缀AND和OR标识全局逻辑。
变量数据类型在宏名称中标识,其中R表示实际数据类型,I表示整数,L表示逻辑。例如,宏PRF_GISUM查找计算节点上整数的总和。
每个全局约简宏都有两个不同的版本:一个采用单个变量参数,另一个采用变量数组。
{
int y;
int x = myid;
y = PRF_GIHIGH1(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宏时,在源代码中的上述命令在所有计算节点上完成之前,不会执行任何其他命令。在调试函数时,同步可能也很有用。