#pragma omp parallel如何将错误引入计算

时间:2016-11-20 11:35:06

标签: c++ openmp atomic

我遇到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()中遇到了竞争状况。但atomiccritical都不起作用。执行顺序不应该是问题,因为我写入的唯一数据(由线程共享)不会在computeFlux()的任何调用中读取。

如果你能给我一些我应该寻找错误的提示,我会很高兴的。

非常感谢,

斯蒂芬

P.s。:我很抱歉我的长篇文章,但我无法在最小的例子中重现问题,因此必须发布部分实际代码。

0 个答案:

没有答案