本文摘要(由AI生成):
本文描述了一个并行UDF(用户定义函数)的设计,特别是关于数据写入的过程。在并行环境中,数据写入过程比串行环境更复杂,需要仔细设计。尽管许多串行代码在并行模式下也能运行,但并不意味着所有串行代码并行化后都能提高计算效率。并行UDF涉及到节点之间数据的加载、传递、接收和发送,以及主机节点对数据的接收和写入文件。特别地,节点0负责收集其他计算节点发送的数据,并将其直接发送给主机。主机节点则负责从节点0接收数据并将其写入文件。在数据传递和写入过程中,还需要注意内存的分配和释放。
本文描述并行UDF的一些限制、处理器标识以及并行文件读写操作。
注:本文内容来自Fluent UDF手册。
在下面的示例中,在DEFINE_ADJUST函数中计算spark体积,并使用C_UDMI将该值存储在用户定义的内存中。然后从用户定义的内存中检索体积并在DEFINE_SOURCE中使用。
#include "udf.h"
/* These variables will be passed between the ADJUST and SOURCE UDFs */
static real spark_center[ND_ND] = {ND_VEC(20.0e-3, 1.0e-3, 0.0)};
static real spark_start_angle = 0.0, spark_end_angle = 0.0;
static real spark_energy_source = 0.0;
static real spark_radius = 0.0;
static real crank_angle = 0.0;
DEFINE_ADJUST(adjust, domain)
{
#if !RP_HOST
const int FLUID_CHAMBER_ID = 2;
real cen[ND_ND], dis[ND_ND];
real crank_start_angle;
real spark_duration, spark_energy;
real spark_volume;
real rpm;
cell_t c;
Thread *ct;
rpm = RP_Get_Real("dynamesh/in-cyn/crank-rpm");
crank_start_angle = RP_Get_Real("dynamesh/in-cyn/crank-start-angle");
spark_start_angle = RP_Get_Real("spark/start-ca");
spark_duration = RP_Get_Real("spark/duration");
spark_radius = RP_Get_Real("spark/radius");
spark_energy = RP_Get_Real("spark/energy");
/* Set the global angle variables [deg] here for use in the SOURCE UDF */
crank_angle = crank_start_angle + (rpm * CURRENT_TIME * 6.0);
spark_end_angle = spark_start_angle + (rpm * spark_duration * 6.0);
ct = Lookup_Thread(domain, FLUID_CHAMBER_ID);
spark_volume = 0.0;
begin_c_loop_int(c, ct)
{
C_CENTROID(cen, c, ct);
NV_VV(dis,=,cen,-,spark_center);
if (NV_MAG(dis) < spark_radius)
{
spark_volume += C_VOLUME(c, ct);
}
}
end_c_loop_int(c, ct)
spark_volume = PRF_GRSUM1(spark_volume);
spark_energy_source = spark_energy/(spark_duration*spark_volume);
Message0("\nSpark energy source = %g [W/m3].\n", spark_energy_source);
#endif
}
DEFINE_SOURCE(energy_source, c, ct, dS, eqn)
{
/* Don't need to mark with #if !RP_HOST as DEFINE_SOURCE is only executed
on nodes as indicated by the arguments "c" and "ct" */
real cen[ND_ND], dis[ND_ND];
if((crank_angle >= spark_start_angle) && (crank_angle < spark_end_angle))
{
C_CENTROID(cen, c, ct);
NV_VV(dis,=,cen,-,spark_center);
if (NV_MAG(dis) < spark_radius)
{
return spark_energy_source;
}
}
return 0.0;
}
注:解释udf不能与infiniband互连一起使用。这种情况下应该使用编译UDF方法。
并行ANSYS Fluent中的每个计算节点都有一个唯一的整数标识符,该标识符存储为全局变量myid。当在并行UDF中使用myid时,它将返回当前计算节点(包括主机)的整数ID。host节点的ID为host(=999999),并存储为全局变量host。node-0节点的ID为0,并被分配给全局变量node_zero。下面是并行ANSYS Fluent中的全局变量列表。
int node_zero = 0;
int host = 999999;
int node_one = 1;
int node_last; /* 返回最后一个计算节点的ID值*/
int compute_node_count; /* 返回计算节点的数量 */
int myid; /*返回当前节点的ID值 */
myid通常用于并行UDF代码中的条件-if语句。下面是一些使用全局变量myid的示例代码。在本例中,首先通过累加计算面thread中面的总数。然后,如果myid不是node-0节点,则使用传递宏PRF_CSEND_INT的消息将面数从所有计算节点传递到node-0节点。
int noface=0;
begin_f_loop(f, tf) /* loops over faces in a face thread and computes number of faces */
{
noface++;
}
end_f_loop(f, tf)
/* Pass the number of faces from node 1,2, ... to node 0 */
#if RP_NODE if(myid!=node_zero)
{
PRF_CSEND_INT(node_zero, &noface, 1, myid);
}
#endif
计算当Fluent以并行模式运行时,计算节点可以同时对数据执行计算。但当数据从一个���通文件���取或写入该文件时,操作必须是连续的,文件必须由访问文件系统的处理器打开和读写。通常情况下,计算节点在没有磁盘空间的专用并行计算机上运行,这意味着所有的数据都必须从host节点读取和/或写入,host节点总是在具有文件系统访问权限的机器上运行,因为其需要读取和写入cas和dat文件。这意味着,现在必须将数据从所有计算节点传递给node-0节点,然后node-0节点将数据传递给host节点,再利用host节点将数据写入文件。这个过程称为“marshalling”。
3.1 读取文件
在并行UDF中读取文件之前, 要将文件从host节点复 制到计算节点,可以使用以下函数:
host_to_node_sync_file(const char* filename)
这可以处理当前工作路径在host与node之间不共享时。对于host节点,输入参数filename为要复 制到node节点的文件路径,而对于node节点,输入参数是复 制文件的节点上的路径。函数执行完成后,host_to_node_sync_file()返回复 制的字节数,否则返回-1。
在下面的示例中,Windows上的host节点将文件从其本地目录e:\udfs\test.bat复制到远程Linux节点上的目录/tmp。
DEFINE_ON_DEMAND(host_to_node_sync)
{
#if RP_HOST
int total_bytes_copied = host_to_node_sync_file("e:\\udfs\\test.dat.h5");
#endif
#if RP_NODE
int total_bytes_copied = host_to_node_sync_file("/tmp");
#endif
printf("Total number of bytes copied is %d\n", total_bytes_copied);
}
3.2 写入文件
在并行模式下写入文件过程较为复杂,其基本步骤为:
下面的示例描述了文件写入过程。
#include "udf.h"
# define FLUID_ID 2
DEFINE_ON_DEMAND(pressures_to_file)
{
/* Different variables are needed on different nodes */
#if !RP_HOST
Domain *domain=Get_Domain(1);
Thread *thread;
cell_t c;
#else
int i;
#endif
#if !RP_NODE
FILE *fp = NULL;
char filename[]="press_out.txt";
#endif
int size; /* data passing variables */
real *array;
int pe;
#if !RP_HOST
thread=Lookup_Thread(domain,FLUID_ID);
#endif
#if !RP_NODE
if ((fp = fopen(filename, "w"))==NULL)
Message("\n Warning: Unable to open %s for writing\n",filename);
else
Message("\nWriting Pressure to %s...",filename);
#endif
/* UDF Now does 2 different things depending on NODE or HOST */
#if RP_NODE
/* Each Node loads up its data passing array */
size=THREAD_N_ELEMENTS_INT(thread);
array = (real *)malloc(size * sizeof(real));
begin_c_loop_int(c,thread)
array[c]= C_P(c,thread);
end_c_loop_int(c,thread)
/* Set pe to destination node */
/* If on node_0 send data to host */
/* Else send to node_0 because */
/* compute nodes connect to node_0 & node_0 to host */
pe = (I_AM_NODE_ZERO_P) ? host : node_zero;
PRF_CSEND_INT(pe, &size, 1, myid);
PRF_CSEND_REAL(pe, array, size, myid);
free(array);/* free array on nodes after data sent */
/* node_0 now collect data sent by other compute nodes */
/* and sends it straight on to the host */
if (I_AM_NODE_ZERO_P)
compute_node_loop_not_zero (pe)
{
PRF_CRECV_INT(pe, &size, 1, pe);
array = (real *)malloc(size * sizeof(real));
PRF_CRECV_REAL(pe, array, size, pe);
PRF_CSEND_INT(host, &size, 1, myid);
PRF_CSEND_REAL(host, array, size, myid);
free((char *)array);
}
#endif /* RP_NODE */
#if RP_HOST
compute_node_loop (pe) /* only acts as a counter in this loop */
{
/* Receive data sent by each node and write it out to the file */
PRF_CRECV_INT(node_zero, &size, 1, node_zero);
array = (real *)malloc(size * sizeof(real));
PRF_CRECV_REAL(node_zero, array, size, node_zero);
for (i=0; i<size; i++)
fprintf(fp, "%g\n", array[i]);
free(array);
}
#endif /* RP_HOST */
#if !RP_NODE
fclose(fp);
Message("Done\n");
#endif
}
并行UDF的��写要远比串行程序复杂,在工程应用时一定要仔细设计。大多数串行代码在并行模式下也能够很好的工作,千万不要以为把串行代码改造成了并行代码就一定能够提高计算效率。