【问题标题】:OpenMP plus reduction on unordered_map<string,double>OpenMP 加上 unordered_map<string,double> 上的缩减
【发布时间】:2018-10-18 03:18:24
【问题描述】:

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

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);
}

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

In this answer 用户声明的归约是为向量的情况定义的。在 unordered_map 的情况下,是否可以类似地定义用户声明的减少?

【问题讨论】:

  • 离题:我会使用umap.find 而不是umap[]。这样一来,您就可以保证不会添加任何新元素,没有多余的未使用代码来添加未找到的元素,并且可以错误检查/断言您确实找到了您要查找的内容。

标签: c++ parallel-processing openmp


【解决方案1】:

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

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

由于地图存储类型std::pair&lt;const T1, T2&gt;(即第一个必须始终为const,您不能修改键),这会导致错误,因为operator=在这种情况下被删除。

出于这个原因,我们最终不得不自己编写整个事情(下面的答案可能会更简洁,我只是硬编码了你的类型......)。

我们可以从std::transform (look at example implementation 2) 的示例开始并修改有问题的部分,但是@Zulan 在 cmets 中提出了一个很好的观点,即同时遍历无序映射可能不是一个好主意(因为它们是,根据定义,未排序)。虽然复制构造函数保留顺序可能有一定意义,但标准似乎无法保证这一点(至少我在任何地方都找不到),因此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;
}

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

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

【讨论】:

  • 我不认为同时遍历迭代器适用于 无序 地图。
  • @Zulan 虽然您的评论起初听起来很合理,但我不太确定。请记住,这里的所有地图都是相同初始无序地图的副本,所以我假设它们的顺序是相同的(尽管标准可能不需要它,但知道会很有趣,让我检查)。
  • @Zulan 根据stackoverflow.com/questions/45620007/…,它似乎不在 C++11 中,但是尝试使用 gcc 和 c++17 的相同示例似乎给出了相同的顺序,但我这样做了同意这听起来不太安全。
  • 您不能依赖订单,因为它不能保证。发生的事情是高度实现定义的。无法保证复制 unorderd_map 会产生相同的顺序。另见stackoverflow.com/questions/51592794/…
  • @Zulan 这很有趣,人们会希望复制构造函数,嗯,复制。但它是一个无序的地图,所以它确实没有理由这样做。
猜你喜欢
  • 2019-06-11
  • 2017-08-27
  • 1970-01-01
  • 1970-01-01
  • 2016-05-12
  • 1970-01-01
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多