如何减少OpenMP中for循环内关键部分的值?

时间:2019-06-27 02:36:14

标签: c++ openmp

编辑

让我们重述一切。我正在OpenMP上实现Bellman-Ford。据我了解,compare步骤和dist的设置必须在关键块中完成,因为更新dist可能会改变compare步骤的结果- -这里有一场数据竞赛。

然后我的问题是,updated_last_round变量不需要在关键块中进行更新。这里有一场数据竞赛,但是唯一的update-to值是true,所以没关系。我担心当前的实现方式是,所有线程都在原子上更新updated_last_round并彼此放慢速度。

bool compare(int v1, int w, int v2) {
    // checks if v1 + w < v2
    if (v1 == INT_MAX) return false;
    if (v2 == INT_MAX) return true;
    return ((v1+w) < v2);
}

vector<int> bellman_ford(Graph& g, int root) {
    vector<int> dist(g.num_nodes());
    # pragma omp parallel for
    for (int i = 0; i < g.num_nodes(); i++)
        dist[i] = INT_MAX; // set INF

    dist[root] = 0;

    int round = 0;
    bool updated_last_round = true;
    // relax procedure
    while (updated_last_round && round < g.num_nodes()) {
        updated_last_round = false;
        #pragma omp parallel for
        for (int u = 0; u < g.num_nodes(); u++) {
            vector<int> neighbors = g.out_neighbors(u);
            vector<int> weights = g.out_weights_neighbors(u);
            #pragma omp parallel for 
            for (int j = 0; j < g.out_degree(u); j++) {
                int v = neighbors[j];
                int weight = weights[j];
                #pragma omp critical
                {
                if (compare(dist[u], weight, dist[v])) { 
                    dist[v] = dist[u] + weight;
                    updated_last_round = updated_last_round || true;
                }
                }
            }
        }
        round += 1;
    }

    /* ... */

    return dist;
}

原始

我正在尝试并行化OpenMP中的一些代码,这些代码需要在并行的for循环内进行原子检查和设置,并且我在每次迭代结束时计算是否已设置至少一个值。

现在,我正在使用reduction(||:updated_last_round)来减少每次迭代结束时的布尔值,但是我不确定这是否会加快速度,因为更新布尔值的实际代码行是在关键部分内。

bool updated_last_round = true

while (updated_last_round) {
  updated_last_round = false;

  #pragma omp parallel for reduction(||:updated_last_round)
  for (/* stuff */) {
    // other stuff
    #pragma omp critical
    {
    if (should_update(vars_to_decide_with)) { 
      // do the important critical update

      // I DON'T want this to be atomically updated, as
      // the data race doesn't matter at all
      updated_last_round = updated_last_round || true;
    }
  }
}

有意义的是,有一种方法可以让关键部分只做关键工作,然后继续设置线程局部的bool值,然后在每次迭代结束时减小局部值。我应该如何实现?

2 个答案:

答案 0 :(得分:1)

首先,从技术上说,同时写入updated_last_round仍然是一种竞争条件,even if you only write the same value

但是,不必担心写入updated_last_round。与关键部分的总体开销相比,这几乎没有关系。 不用担心,因为每个微小的内部循环迭代中关键部分的开销。考虑到dist[v]dist[u]的读写依赖性,我看不到任何解决关键部分的方法。

如何添加减少量,并仍在关键部分设置updated_last_round。从理论上讲,这将加快写入速度,因为它现在是本地的,而不是具有缓存失效的共享变量。但是,与关键部分的巨大开销相比,这并不重要。

注意:并行化的唯一好处是out_*neighbors函数非常昂贵。但是我假设它们只是返回一个固定向量-出于性能原因,您应该返回并由const&捕获。

如果您想有效地并行化此算法,则必须考虑对数据进行分区以解决依赖性。注意:不幸的是,搜索“ Bellman-Ford OpenMP ”节目会给出一些非常错误的尝试,例如this upvoted and accepted answer on SO

除此之外,请不要使用嵌套并行性(除非您真正知道自己在做什么,否则请parallel中使用parallel)。并行执行最安全的最外部循环,如果可以带来性能优势,请使用collapse

在尽可能局部声明变量方面也做得很好-这使得对竞赛条件的推理变得容易得多。对于矢量副本,这可能有些棘手-无论如何,它们应该都是const&

答案 1 :(得分:0)

  

有意义的是,有一种方法可以让关键部分   只做关键的事情,然后继续设置线程局部的布尔值   值,然后在每次迭代结束时减小局部值。怎么样   我应该实现吗?

像这样? 在我看来,这显然是您刚才描述的实现。 我已将测试移至关键部分之外。没有更多信息,目前尚不清楚这是否安全...

bool updated_last_round = true

while (updated_last_round) {
  updated_last_round = false;

  #pragma omp parallel for reduction(||:updated_last_round)
  for (/* stuff */) {
    // other stuff
    bool updated_this_iteration = false;

    if (should_update(vars_to_decide_with)) 
     { 
       #pragma omp critical
       {
          // do the important critical update
       }
       // Set local, per-iteration, value
       updated_this_iteration = true;
    }
    updated_last_round = updated_last_round ||  updated_this_iteration;
  }
}