OpenMP 加上 unordered_map<string,double> 上的缩减

Gon*_*gas 5 c++ parallel-processing openmp

我想并行化一个 for 循环,其中更新 unordered_map 的值:

unordered_map<string,double> umap {{"foo", 0}, {"bar", 0}};

#pragma omp parallel for reduction(my_reduction:umap)
for (int i = 0; i < 100; ++i)
{
    // some_string(i) would return either "foo" or "bar"
    umap[some_string(i)] += some_double(i);
}
Run Code Online (Sandbox Code Playgroud)

因此,unordered_map 中不会创建新条目,只会更新现有条目的总和。

在这个答案中,用户声明的归约是针对向量的情况定义的。在 unordered_map 的情况下,用户声明的归约是否可以类似地定义?

Qub*_*bit 3

可以使用与您链接的答案中采用的方法类似的方法来完成。我们面临的一个问题是,std::transform在地图方面使用了一条不幸的线。

//GCC version, but the documentation suggests the same thing. 
*__result = __binary_op(*__first1, *__first2); 
Run Code Online (Sandbox Code Playgroud)

由于映射存储类型std::pair<const T1, T2>(即第一个必须始终是常量,您不能修改键),这会导致错误,因为operator=在这种情况下被删除。

由于这个原因,我们最终不得不自己编写整个事情(接下来的答案可以变得更清晰,我只是硬编码了你的类型......)。

我们可以从示例开始std::transform查看示例实现 2)并修改有问题的部分,但是 @Zulan 在评论中提出了一个很好的观点,即同时遍历无序映射可能不是一个好主意(因为根据定义,它们是这样的)未订购)。虽然复制构造函数保留顺序可能有一定道理,但标准似乎并不能保证这一点(至少我在任何地方都找不到它),因此所采用的方法std::transform变得非常无用。

我们可以通过稍微不同的减少来解决这个问题。

#include <unordered_map>
#include <string>
#include <iostream>
#include <utility>

void reduce_umaps(\
    std::unordered_map<std::string, double>& output, \
    std::unordered_map<std::string, double>& input)
{
    for (auto& X : input) {
      output.at(X.first) += X.second; //Will throw if X.first doesn't exist in output. 
    }
}

#pragma omp declare reduction(umap_reduction : \
    std::unordered_map<std::string, double> : \
    reduce_umaps(omp_out, omp_in)) \
    initializer(omp_priv(omp_orig))

using namespace std;

unordered_map<string, double> umap {{"foo", 0}, {"bar", 0}};

string some_string(int in) {
    if (in % 2 == 0) return "foo";
    else return "bar";
}

inline double some_double(int in) {
    return static_cast<double>(in);
}

int main(void) {
#pragma omp parallel for reduction(umap_reduction:umap)
for (int i = 0; i < 100; ++i) {
        umap.at(some_string(i)) += some_double(i);
    }
    std::cerr << umap["foo"] << " " << umap["bar"] << "\n";
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

您还可以将其概括为允许在并行循环中添加键,但是除非添加的键的数量仍然远小于增加值的次数,否则并行性不会很好。

作为最后的旁注,我替换umap[some_string(i)]umap.at(some_string(i)), 以避免意外添加元素,就像评论中建议的那样,但这find并不是用于此目的的最实用的功能。