我试图用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);
}
}
}
答案 0 :(得分:0)
我正在做的假设,因为从你的样本中看不出来:
每个block
(即OMP迭代)都有自己的GRBModel
,您访问的每个GRBConstr
,GRBVar
等都与正确的模型相关联(来自同一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迭代即使在完美同步的情况下也能以串行方式执行。
我无法告诉你它是哪个选项,或者它是否位于中间某个位置。它完全取决于linkVarsInBlock
和blocksContainingLinkVar
集合中的值。也许这些街区是完全独立的。也许您有数百个相互依赖的块,有些可以通过巧妙的分发和同步独立处理。或者每个块可能依赖于前面的每个块,因此您根本无法并行化。
PS:考虑使用基于范围的for循环。当你只想访问每个包含的对象一次时,你的代码中有太多的样板开销来声明,分配和比较容器的迭代器。这正是基于范围的for循环的存在,它们会使你的代码更具可读性。