Как ускорить параллель openmp с упорядоченным предложением? - PullRequest
0 голосов
/ 11 мая 2018

Я попытался распараллелить цикл for некоторого последовательного кода C ++ с openmp.Сложность заключается в правильной обработке

linkingVarVals 

. Чтобы получить те же результаты, что и в последовательном коде, я использовал упорядоченное предложение openmp.Следующий подход, кажется, работает в моем случае.Но, к сожалению, код 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 голосов
/ 11 мая 2018

Предположения, которые я делаю, потому что они не очевидны из вашего образца:

  • Каждая block (т.е. итерация OMP) имеет свой собственный GRBModel, и каждый доступ к GRBConstr, GRBVar и т. Д. Связан с правильной моделью (той же самой * 1009). *) уже.

  • libgurobi позволяет безопасно выполнять параллельные операции над указанными выше переменными, если они принадлежат различным моделям.

Вот проблемы с вашим кодом:

  • Абсолютно бессмысленно делить newRHS между потоками. Все, что вы делаете, это читаете, а затем сразу пишите это. Сделайте его локальной переменной, у вас проблемы только с общим доступом.

  • Некоторые потоки могут записывать в linkingVarVals в цикле 4, в то время как другие потоки читают из него в цикле 3. Здесь это зависит от ваших данных.

    • Вариант a): из-за того, как заполнены set s, итерация OMP для блока block всегда читает только из linkingVarVals.at({block, ...}), а не из любого другого блока. В этом случае все просто: вы должны убедиться, что карта linkingVarVals никогда не перераспределяется записями в цикле 4 (т. Е. operator[] не создает никаких записей - просто создайте все записи заранее), и вы должны убедиться, что эти записи происходят в последовательном порядке (поместите упорядоченную область вокруг всей петли 4).

    • Опция b): записи в цикле 4 влияют на последующие newRHS = ... чтения. В этом случае это трудно, если не невозможно, распараллелить, потому что каждая итерация OMP может начать читать linkingVarVals только после того, как предыдущая итерация OMP полностью закончилась, записывая в linkingVarVals. Зависимости заставили бы итерации OMP выполняться последовательно даже при совершенной синхронизации.

Я не могу сказать вам, какой это вариант или он где-то посередине. Это полностью зависит от значений в ваших наборах linkVarsInBlock и blocksContainingLinkVar. Может быть, блоки полностью независимы. Может быть, у вас есть сотни взаимозависимых блоков, и некоторые из них могут быть обработаны независимо с умным распределением и синхронизацией. Или, может быть, каждый блок зависит от каждого предыдущего, поэтому вы не можете распараллелить вообще.

PS: рассмотрите возможность использования циклов на основе диапазона. В вашем коде так много дополнительных издержек от объявления, назначения и сравнения итераторов контейнеров, когда вы просто хотите посетить каждый содержащийся объект один раз. Именно для этого существуют циклы, основанные на диапазонах, и они сделают ваш код намного более читабельным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...