openMP - 需要原子或减少子句

问题描述:

我正在使用openMP来并行化几个语句。我正在使用平行于构造。并行化for循环看起来像:openMP - 需要原子或减少子句

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(); 
    } 
} 

这是平均工作执行时间显着减少,结果为如预期。然而,不时,尤其是对于大的问题(在我迭代的大数字,数据的大数量的拷贝构造函数调用

model_ = new Model(varModel_); 

,别人复制?),它坠毁。调用堆栈以诸如qAtomicBasic(它是一个用C++/Qt编写的程序),QHash等类为终点,并且由于内存中的并发读/写访问,我有一个想法它崩溃了。

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

在我的代码的可能的缺陷我识别是

  • functionEvaluation可以由多个线程进行修改声明同时

  • 拷贝构造

    model_ =新模型(varModel_);

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

而且,我只看到两个方面的改进(我不能测试直到几天,但我求教反正):

  • 添加原子条款,使functionEvalution没有同时写在

  • 增加操作符减少(+,functionEvaluation),所以关于获得functionEvaluation是并发处理的自动

做次这些解决方案似乎能够精确地解决问题,而且一般来说效率更高?问题在于我写的代码在哪里?什么是解决方案?

非常感谢!

第一个观察结果是,正如您已经注意到的那样,同时修改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; 
} 

需要注意的是,由于固有的浮点不准确,这个代码可能产生不同的结果来自顺序代码。这是不可避免的。

+0

非常感谢! Konrad,我没有看到没有指针的做法:调用拷贝构造函数'Model()'接受一个参数,它是一个指向Model的指针,'duplicate()'接受一个指向Command的指针。是否更多的内存消耗/使用指针的时间,即使我在循环中删除它们? – octoback 2012-04-14 20:03:20

+0

@dlib第一个问题是:为什么这些函数需要指针作为参数?但是,如果这不能改变,那么你可以完全按照你在评论中写的内容来做。在这里使用堆分配对堆栈分配没有任何好处,使事情更加复杂(手动内存管理),并增加了一个小的性能开销。另外,请参阅我的更新回答,我之前忽略了代码中的另一个大(!)错误。 – 2012-04-14 20:06:05

+0

这是没有错误,它的复制粘贴误解(耻辱),我上面修改。我没有看到如何在这里使用堆栈分配。模型(&model_)不适合,因为我想在新实例model_中复制varModel;我不能没有复制构造函数,即与新的。 – octoback 2012-04-14 20:20:03

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

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

+0

谢谢!我没有看到没有指针的做法:调用拷贝构造函数Model()接受一个参数,它是一个指向Model的指针,而duplicate()接受一个指向Command的指针。 – octoback 2012-04-14 20:09:02

+0

@dlib:你不能改变它们直接接受值而不是指针吗? – Tudor 2012-04-14 20:09:49

+0

不,我不能!这是个大软件,有些东西不是我的。 – octoback 2012-04-14 20:12:13