【问题标题】:openMP - need for atomic or reduction clausesopenMP - 需要原子或缩减子句
【发布时间】:2012-04-26 17:54:44
【问题描述】:

我正在使用 openMP 来并行化一些语句。我正在使用 parallel for 构造。并行化的 for 循环如下所示:

double solverFunction::apply(double* parameters_ , Model* varModel_)  
{
     double functionEvaluation = 0;
     Command* command_ = 0;
     Model* model_ = 0;

     #pragma omp parallel for  shared (functionEvaluation) private (model_,command_)
     for (int i=rowStart;i<rowEnd+1;i++)
     {
         model_ = new Model(varModel_);
         model_->addVariable("i", i);
         model_->addVariable("j", 1);
         command_ = formulaCommand->duplicate(model_);
         functionEvaluation += command_->execute().toDouble();
     }
}

平均而言工作正常。执行时间显着减少,结果符合预期。但是,有时,特别是对于大问题(i 上的大量迭代,复制构造函数调用中要复制的大量数据

 model_ = new Model(varModel_);

,其他人?),它崩溃了。调用堆栈以 qAtomicBasic(它是用 C++/Qt 编写的程序)、QHash 等类结束,我知道它会因为内存中的并发读/写访问而崩溃。

但是,model_ 和 command_ 是私有的,因此每个线程处理每个线程的副本。在变量 model_ 中,我复制了 varModel_,以便线程不会更改传入参数的指针。同样,command_ 是成员变量 formulaCommand 的副本(duplicate 大致是一个复制构造函数)。

我发现的代码中可能存在的缺陷是

  • functionEvaluation 可以被多个线程同时修改

  • 语句中的复制构造函数

    model_ = new Model(varModel_);

读取内存中 varModel_ 的成员以构造新的 (model_) 实例。对 varModel_ 数据成员的并发访问可能会发生,尽管这不是要在此处更改它们的值,而只是读取它们(将它们影响到其他变量)。

另外,我只看到了两个改进(我要几天才能测试,但我还是征求意见):

  • 添加原子子句,使得functionEvalution不会并发写入

  • 添加运算符归约(+,functionEvaluation),以便自动处理访问functionEvaluation的并发

这些解决方案是否似乎准确地解决了问题,总体上哪种更有效?我写的代码哪里有问题?什么是解决方案?

非常感谢!

【问题讨论】:

    标签: c++ multithreading multiprocessing openmp


    【解决方案1】:

    第一个观察结果是,正如您自己注意到的那样,同时修改 functionEvaluation 是一个坏主意。它失败。

    另一方面,varModel_ 的只读访问不是问题。复制构造函数调用也不是(但它在哪里?你的代码没有显示它)。

    不相关地,在 C++ 中使用 private 子句是个坏主意。只需在并行块内部声明线程私有变量(在本例中为for 循环)。

    我也不明白你为什么在这里使用指针。它们的使用没有直接意义——改用堆栈分配的对象。

    以下修改后的代码应该可以工作(我还冒昧地统一了编码风格……为什么尾随下划线?):

    double solverFunction::apply(double parameters, Model const& varModel)
    {
         double result = 0;
    
         #pragma omp parallel for reduction(+:result)
         for (int i = rowStart; i < rowEnd + 1; ++i)
         {
             Model model(varModel);
             mode.addVariable("i", i);
             mode.addVariable("j", i);
             Command command = formulaCommand->duplicate(model);
             result += command.execute().toDouble();
         }
    
         return result;
    }
    

    请注意,由于固有的浮点不准确性,此代码可能会产生与顺序代码不同的结果。这是不可避免的。

    【讨论】:

    • 非常感谢! Konrad,我看不出没有指针怎么办:调用复制构造函数Model() 接受一个参数,它是一个指向模型的指针,duplicate() 同样接受一个指向命令的指针。即使我在循环中删除它们,使用指针是否会消耗更多内存/时间?
    • @dlib 第一个问题是:为什么这些函数将指针作为参数?但如果这不能改变,那么你可以完全按照你在评论中写的那样做。在这里使用堆分配与堆栈分配相比没有任何好处,使事情变得更复杂(手动内存管理)并增加了一点性能开销。另外,请参阅我更新的答案,我之前忽略了您代码中的另一个大 (!) 错误。
    • 这不是bug,是复制粘贴的误会(耻辱),我在上面修改了。我在这里看不到如何使用堆栈分配。 Model(&model_) 不合适,因为我想将 varModel 复制到新实例 model_ 中;如果没有复制构造函数,我就无法工作,那就是 new。
    • @dlib 请参阅更新以获取固定代码。关于复制构造函数:这只是一个构造函数,它接受类类型的 (const) 引用。在您的代码中不是这种情况,因为您正在传递一个指针。复制构造函数也与new 无关(我的代码没有使用new,但它仍在调用模型的复制构造函数)。
    • 你应该使用归约子句而不是求和到一个向量和调用累加。
    【解决方案2】:

    同时修改functionEvaluation绝对是你代码中的一个问题,最好的处理方法是reduction子句。

    另一个问题是,您通过并行调用new 来分配堆内存,这对于许多迭代来说绝不是一个好主意,因为对new 的调用存在系统范围的锁定。考虑切换到堆栈分配,因为堆栈是每个线程私有的,而堆是共享的。

    【讨论】:

    • 谢谢!我看不出没有指针怎么办:调用复制构造函数 Model() 接受一个参数,它是一个指向模型的指针,而 duplicate() 类似地接受一个指向命令的指针。
    • @dlib:你不能把它们改成直接接受值而不是指针吗?
    • @dlib:那你可以尝试传入model_的地址:&amp;model_
    • 我不这么认为:在循环中,我正在创建 model_,并将 varModel 作为参数传递。我必须复制 varModel_,并且必须使用模型复制构造函数(带有指向模型的指针的参数)。我没有看到解决方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-01
    • 1970-01-01
    • 2014-03-02
    • 2021-03-13
    • 1970-01-01
    相关资源
    最近更新 更多