OpenMP 加上 unordered_map<string,double> 上的减少
OpenMP plus reduction on unordered_map<string,double>
我想并行化一个 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);
}
因此,不会创建 unordered_map 中的新条目,只会用总和更新现有条目。
用户声明的归约是为矢量的情况定义的。在 unordered_map 的情况下,是否可以类似地定义用户声明的减少?
可以使用与您链接的答案中采用的方法类似的方法来完成。我们面临的一个问题是 std::transform
在涉及地图时使用了一条不幸的线。
//GCC version, but the documentation suggests the same thing.
*__result = __binary_op(*__first1, *__first2);
因为地图存储类型 std::pair<const T1, T2>
(即第一个必须始终是 const,您不能修改键),这会导致错误,因为在这种情况下 operator=
被删除.
出于这个原因,我们最终不得不自己编写整个内容(下面的答案可以更清晰,我只是硬编码了你的类型...)。
我们可以从 std::transform
(look at example implementation 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;
}
您也可以对此进行概括,以允许在并行循环中添加键,但是除非添加的键的数量仍然远小于您增加值的次数,否则这不会很好地并行化。
作为最后的旁注,我将 umap[some_string(i)]
替换为 umap.at(some_string(i))
,以避免意外添加元素,就像评论中建议的那样,但 find
并不是最实用的为此目的而发挥作用。
我想并行化一个 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);
}
因此,不会创建 unordered_map 中的新条目,只会用总和更新现有条目。
可以使用与您链接的答案中采用的方法类似的方法来完成。我们面临的一个问题是 std::transform
在涉及地图时使用了一条不幸的线。
//GCC version, but the documentation suggests the same thing.
*__result = __binary_op(*__first1, *__first2);
因为地图存储类型 std::pair<const T1, T2>
(即第一个必须始终是 const,您不能修改键),这会导致错误,因为在这种情况下 operator=
被删除.
出于这个原因,我们最终不得不自己编写整个内容(下面的答案可以更清晰,我只是硬编码了你的类型...)。
我们可以从 std::transform
(look at example implementation 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;
}
您也可以对此进行概括,以允许在并行循环中添加键,但是除非添加的键的数量仍然远小于您增加值的次数,否则这不会很好地并行化。
作为最后的旁注,我将 umap[some_string(i)]
替换为 umap.at(some_string(i))
,以避免意外添加元素,就像评论中建议的那样,但 find
并不是最实用的为此目的而发挥作用。