如何使用ordered子句加速openmp并行?

时间:2018-05-11 13:08:45

标签: c++ openmp

我试图用openmp并行化一些顺序c ++代码的for循环。

的正确处理方法存在困难
linkingVarVals 

为了获得与顺序代码相同的结果,我使用了openmp的ordered子句。以下方法似乎适用于我的情况。但不幸的是,openmp代码实际上比顺序执行慢。据推测,这是由有序条款引起的。在我的案例中有什么方法可以加速openmp实现?

typedef tuple<int,int,int> key3;
struct key3_hash : public std::unary_function<key3, std::size_t> {
    std::size_t operator()(const key3& k) const {
        return std::get<0>(k) ^ std::get<1>(k) ^ std::get<2>(k);
    }
};

struct key3_equal : public std::binary_function<key3, key3, bool> {
    bool operator()(const key3& v0, const key3& v1) const {
        return (std::get<0>(v0) == std::get<0>(v1) &&
        std::get<1>(v0) == std::get<1>(v1) &&
        std::get<2>(v0) == std::get<2>(v1));
    }
};

 typedef tuple<int,int> key2;
 struct key2_hash : public std::unary_function<key2, std::size_t> {
     std::size_t operator()(const key2& k) const {
         return std::get<0>(k) ^ std::get<1>(k);
     }
 };

 struct key2_equal : public std::binary_function<key2, key2, bool> {
     bool operator()(const key2& v0, const key2& v1) const {
         return (std::get<0>(v0) == std::get<0>(v1) &&
                 std::get<1>(v0) == std::get<1>(v1));
     }
 };

typedef unordered_map<key3, double, key3_hash, key3_equal> CoeffMap;
typedef unordered_map<key3, GRBVar, key3_hash, key3_equal> VarMap;
typedef unordered_map<key2, double, key2_hash, key2_equal> ValueMap;
typedef unordered_map<key3, GRBConstr, key3_hash, key3_equal> ConstrMap;

void myalgorithm(GRBModel model,
const vector<GRBModel*>& submips,
const set<string>& linkingvarnames,
const map<string,int>& nametoidxmap,
const map<int,string>& idxtonamemap,
const map<int,set<int> >& linkvaridxtoblock,
const map<int,set<int> >& blocktolinkvaridx)
{
    size_t nBlocks = submips.size();
    size_t nVars = model.get(GRB_IntAttr_NumVars);
    CoeffMap slackPosCoeffs;
    CoeffMap slackNegCoeffs;
    VarMap slackPosVars;
    VarMap slackNegVars;
    ValueMap linkingVarVals;
    ConstrMap couplingCons;

    // the following code shows the connection between 
    // submips[block] and couplingCons
    for (size_t block = 0; block < nBlocks; ++block) {
        set<int> linkVarsInBlock = blocktolinkvaridx.at(block);
        for (set<int>::const_iterator it = linkVarsInBlock.begin(), ei = linkVarsInBlock.end(); it != ei; ++it) {
            int linkVarIdx = *it;
            set<int> blocksContainingLinkVar = linkvaridxtoblock.at(linkVarIdx);
            for (set<int>::const_iterator jt = blocksContainingLinkVar.begin(), ej = blocksContainingLinkVar.end(); jt != ej; ++jt) {
                int blockContainingLinkVar = *jt;
                if (blockContainingLinkVar != block) {
                    auto idx2 = make_tuple(blockContainingLinkVar, linkVarIdx);
                    auto idx3 = make_tuple(block, blockContainingLinkVar, linkVarIdx);
                    stringstream constrName;
                    constrName << idxtonamemap.at(linkVarIdx) << "_Coupling_Block_" << blockContainingLinkVar;
                    couplingCons[idx3] = submips[block]->addConstr(submips[block]->getVarByName(idxtonamemap.at(linkVarIdx)) + slackPosVars.at(idx3) - slackNegVars.at(idx3) == linkingVarVals.at(idx2), constrName.str());
                }
            }
        }
        submips[block]->update();
    }

#if defined(_OPENMP)
    // set number of openmp threads
    unsigned int numprocs = omp_get_num_procs();
    cout << "=== NumProcessors " << numprocs << endl;
    unsigned int numthreads = numprocs > 1 ? numprocs :        
    std::min((int)nBlocks, 4);
    omp_set_num_threads(numthreads);
    cout << "=== OpenMP threads " << numthreads << endl;
#endif

    double newRHS;
#pragma omp parallel for ordered schedule(dynamic)
    // 1. loop
    for (size_t block = 0; block < nBlocks; ++block) {
        set<int> linkVarsInBlock = blocktolinkvaridx.at(block);

        // 2. loop
        for (set<int>::const_iterator it = linkVarsInBlock.begin(), ei = linkVarsInBlock.end(); it != ei; ++it) {
            int linkVarIdx = *it;
            set<int> blocksContainingLinkVar = linkvaridxtoblock.at(linkVarIdx);

            // 3. loop
            for (set<int>::const_iterator jt = blocksContainingLinkVar.begin(), ej = blocksContainingLinkVar.end(); jt != ej; ++jt) {
                int blockContainingLinkVar = *jt;
                if (blockContainingLinkVar != block) {
                    auto idx3 = make_tuple(block, blockContainingLinkVar, linkVarIdx);
                    auto idx2 = make_tuple(blockContainingLinkVar, linkVarIdx);
                    GRBConstr c = couplingCons.at(idx3);
                    string name = c.get(GRB_StringAttr_ConstrName);
                    double oldRHS = c.get(GRB_DoubleAttr_RHS);
#pragma omp ordered
                    {
                        newRHS = linkingVarVals.at(idx2);
                    }
                    c.set(GRB_DoubleAttr_RHS, newRHS);

                    slackPosVars.at(idx3).set(GRB_DoubleAttr_Obj, slackPosCoeffs.at(idx3));
                    slackNegVars.at(idx3).set(GRB_DoubleAttr_Obj, slackNegCoeffs.at(idx3));
                }
            }
        }

        submips[block]->optimize();

        // 4. loop
        for (set<int>::const_iterator it = linkVarsInBlock.begin(), ei = linkVarsInBlock.end(); it != ei; ++it) {
            int linkVarIdx = *it;
            auto idx2 = make_tuple(block, linkVarIdx);

            linkingVarVals[idx2] = submips[block]->getVarByName(idxtonamemap.at(linkVarIdx)).get(GRB_DoubleAttr_X);
        }
    }
}   

1 个答案:

答案 0 :(得分:0)

我正在做的假设,因为从你的样本中看不出来:

  • 每个block(即OMP迭代)都有自己的GRBModel,您访问的每个GRBConstrGRBVar等都与正确的模型相关联(来自同一block的那个)已经。

  • libgurobi只要它们属于不同的模型,就可以安全地允许对上述变量进行并发操作。

以下是您的代码的问题:

  • 在线程之间共享newRHS绝对没有意义。你所做的只是阅读它然后立即写下来。将它设为局部变量,只会让它共享产生问题。

  • 某些线程可能在循环4中写入linkingVarVals,而其他线程正在循环3中读取它。这取决于您的数据。

    • 选项a):由于set的填充方式,块block的OMP迭代只能从linkingVarVals.at({block, ...})读取,绝不会读取任何其他块。在这种情况下,事情很容易:您必须确保linkingVarVals映射永远不会被循环4中的写入重新分配(即operator[]没有创建任何条目 - 只需事先创建所有条目)并且您必须确保那些写入按顺序发生(将有序区域放在整个循环4周围)。

    • 选项b):循环4中的写入对后续newRHS = ...读取有影响。在这种情况下,即使不是不可能并行化也很难,因为每次OMP迭代只能在前一个OMP迭代完全写入linkingVarVals后开始读取linkingVarVals。依赖关系将强制OMP迭代即使在完美同步的情况下也能以串行方式执行。

我无法告诉你它是哪个选项,或者它是否位于中间某个位置。它完全取决于linkVarsInBlockblocksContainingLinkVar集合中的值。也许这些街区是完全独立的。也许您有数百个相互依赖的块,有些可以通过巧妙的分发和同步独立处理。或者每个块可能依赖于前面的每个块,因此您根本无法并行化。

PS:考虑使用基于范围的for循环。当你只想访问每个包含的对象一次时,你的代码中有太多的样板开销来声明,分配和比较容器的迭代器。这正是基于范围的for循环的存在,它们会使你的代码更具可读性。