我遇到OpenMP并行化问题。我研究了一种自编的有限体积代码(用于解决流动问题的数值方法)。为了提高性能,我使用OpenMP并行化。
我工作的数据结构是存储实际模拟数据的单元格,以及计算更改单元格数据的通量的面。一个面连接到两个单元,一个单元连接到四个接口。
仅针对每个Cell工作的操作(没有来自其他单元格或面的数据)与#pragma omp parallel for
很好地并行化。然而,通量计算的并行化不起作用。在该计算中,不仅使用其自身的面部数据而且使用相邻单元的数据。
这是通量计算和单元更新的并行化:
#pragma omp parallel for
for ( int id = 0; id < numberOfInterfaces; ++id )
computeFlux(id);
#pragma omp parallel for
for ( int id = 0; id < numberOfCells; ++id )
if ( !isGhostCell(id) )
updateCell(id);
computeFlux()
使用CellData
中的数据来计算一个Face对单元格的贡献。这些贡献在CellUpdate
中累积,因为可怜的面孔对细胞有贡献。只有当所有面孔都为细胞做出贡献时,我才会遍历所有细胞并使用CellData
中累积的贡献更新Cell
。这意味着我在computeFlux()
中读取的数据在通量计算期间不会改变。因此,对于不同面孔调用computeFlux()
的顺序应该不相关。
computeFlux()
方法执行许多计算:
void GKSSolver::computeFlux(const idType id)
{
const int NUMBER_OF_MOMENTS = 7;
PrimitiveVariable prim;
ConservedVariable normalGradCons;
ConservedVariable timeGrad;
ConservedVariable InterfaceFlux;
double a[4];
double b[4] = {0.0, 0.0, 0.0, 0.0};
double A[4];
double MomentU[NUMBER_OF_MOMENTS];
double MomentV[NUMBER_OF_MOMENTS];
double MomentXi[NUMBER_OF_MOMENTS];
prim = reconstructPrimPiecewiseConstant(id);
normalGradCons = differentiateConsNormal(id, prim.rho);
this->global2local(id, prim);
this->global2local(id, normalGradCons);
this->computeMicroSlope(prim, normalGradCons, a);
this->computeMoments(prim, MomentU, MomentV, MomentXi, NUMBER_OF_MOMENTS);
timeGrad = this->computeTimeDerivative(MomentU, MomentV, MomentXi, a, b);
this->computeMicroSlope(prim, timeGrad, A);
double tau = 2.0*prim.L * this->fluidParam.nu;
double timeCoefficients[3] = { this->dt, -tau*this->dt, 0.5*this->dt*this->dt - tau*this->dt };
InterfaceFlux = this->assembleFlux(MomentU, MomentV, MomentXi, a, b, A, timeCoefficients, prim, this->getInterfaceArea(id), tau);
this->local2global( id, InterfaceFlux );
// ========================================================================
this->applyFlux(id, InterfaceFlux);
}
注释行上方的所有方法仅适用于computeFlux()
内的局部变量(因此应该是threadprivate)或从非局部变量读取数据。唯一的非本地写操作(写入共享数据)发生在applyFlux()
:
void GKSSolverPush::applyFlux(idType id, ConservedVariable flux)
{
if( this->InterfaceAdd2Cell[id][0] )
{
#pragma omp atomic
this->CellUpdate[ this->Interface2Cell[id][0] ].rho += flux.rho ;
#pragma omp atomic
this->CellUpdate[ this->Interface2Cell[id][0] ].rhoU += flux.rhoU;
#pragma omp atomic
this->CellUpdate[ this->Interface2Cell[id][0] ].rhoV += flux.rhoV;
#pragma omp atomic
this->CellUpdate[ this->Interface2Cell[id][0] ].rhoE += flux.rhoE;
}
if( this->InterfaceAdd2Cell[id][1] )
{
#pragma omp atomic
this->CellUpdate[ this->Interface2Cell[id][1] ].rho -= flux.rho ;
#pragma omp atomic
this->CellUpdate[ this->Interface2Cell[id][1] ].rhoU -= flux.rhoU;
#pragma omp atomic
this->CellUpdate[ this->Interface2Cell[id][1] ].rhoV -= flux.rhoV;
#pragma omp atomic
this->CellUpdate[ this->Interface2Cell[id][1] ].rhoE -= flux.rhoE;
}
}
applyFlux()
中的写操作不是线程安全的,因为多个面(可能由不同的线程处理)可能同时更新特定单元的CellUpdate
。因此,我使用#pragma omp atomic
保护这些操作。
当我使用和不使用#pragma omp parallel for
运行程序时,我得到不同的结果意义,并行化在某些时候会破坏我的计算。我不知道这一点。
要识别错误,我尝试了不同的OpenMP pragma。
在非线程安全部分周围添加关键部分不解决问题。
#pragma omp critical
this->applyFlux(id, InterfaceFlux);
使非线程安全部件有序解决问题:
#pragma omp ordered
this->applyFlux(id, InterfaceFlux);
#pragma omp parallel for
的并行化在哪些方面会在计算中引入错误?
局部变量(在共享工作负载内部调用的函数范围内)是否总是私有的?或者他们也可以分享?那我怎么称他们为私人呢?
阅读数据时是否也会出现竞争条件?多面(多线程)可能在我的代码中模拟地读取相同的单元格数据。据我所知,这不应该产生种族歧视,但我可能错了。
我的第一个猜测是我在applyFlux()
中遇到了竞争状况。但atomic
和critical
都不起作用。执行顺序不应该是问题,因为我写入的唯一数据(由线程共享)不会在computeFlux()
的任何调用中读取。
如果你能给我一些我应该寻找错误的提示,我会很高兴的。
非常感谢,
斯蒂芬
P.s。:我很抱歉我的长篇文章,但我无法在最小的例子中重现问题,因此必须发布部分实际代码。