自定义 OMP 缩减 在 std::map 上
custom omp reduction on std::map's
所以我遇到的问题或多或少如下:
我有一组相对较大的数据,其中总是包含一对标识符和与之相关的值。因此,相对较少存在不同但任意的标识符。
在c++中,这可能看起来像一个std::vector<std::pair<size_t, double> >
。
我现在想要生成一个std::map
,它告诉我们每个标识符的所有值的总和,所以在本例中为std::map<size_t, double>
。
所以对于的输入
std::vector<std::pair<size_t, double>> typeDoubleVec{{1, 2.}, {3, 4.}, {1, 3.}, {3, 5.}, {2, 1.}};
我想要一张等于的地图
std::map<size_t, double> result{{1, 5.}, {2, 1.}, {3, 9.}}
执行此任务的函数如下所示。因此,第二个输入矢量指定存在哪些标识符:
std::map<size_t, double> foo(const std::vector<std::pair<size_t, double>> &typeDoubleVec, const std::vector<size_t> &typeVec) {
// create the map that contains our return values.
std::map<size_t, double> mymap;
// ensure that mymap contains all identifiers.
for (auto &elem : typeVec) {
mymap[elem];
}
// iterate over the elements
for (size_t i = 0; i < typeDoubleVec.size(); ++i) {
mymap.at(typeDoubleVec[i].first) += typeDoubleVec[i].second;
}
return mymap;
}
有人知道如何使用OpenMP加快速度吗?我认为这是有效的,你需要一个自定义的OpenMP减少?
所以我自己找到了答案:有两种方法可以做到这一点,一种是自定义减少,另一种是关键部分。其中,我目前推荐后者,主要是因为前者在当前clang编译器上已损坏(v9.0.0,修复程序已在trunk/master中)。
OpenMP并行,用于自定义缩减
解决这个问题的第一种方法是使用OpenMP减少,通常看起来是这样的:
// this does not work for maps!
#pragma omp parallel for reduction(+: mymap)
由于没有为std::map
定义内置的reduction+
,因此剪切的代码将不会编译。
相反,我们将不得不定义我们自己的减排。快速了解一些OpenMP规范(https://www.openmp.org/spec-html/5.1/openmpsu117.html#x152-1790002.21.5.7)揭示了定义自定义减少的以下语法:
#pragma omp declare reduction(reduction-identifier : typename-list :
combiner) [initializer-clause] newline
- 缩减标识符:这是我们可以为自定义缩减指定的名称
- typename列表:这是一个定义了该缩减的类型名称列表。对我们来说,这是
std::map<size_t, double>
- combiner:这是进行实际还原的表达式。它以
omp_in
和omp_out
作为输入,并应将组合结果存储在omp_out
中。对于简单的+
还原,这就是omp_out += omp_in
- initializer子句:这是一个可选表达式,其形式应为
initializer(expression)
。如果缺少,还原变量的线程本地副本将默认初始化。如果存在,则表达式的形式必须为omp_priv = initializer
或omp_priv = function-name(argument-list)
。它也可以使用omp_orig
,它对应于归约变量的初始值 - 在杂注的末尾需要一个换行符
在这种情况下,我们希望添加具有相同键的两个映射的值。这可以在这样的函数中完成:
void mapAdd(std::map<size_t, double> &inout, std::map<size_t, double> &in) {
for (auto initer = in.begin(), outiter = inout.begin(); initer != in.end(); ++initer, ++outiter) {
outiter->second += initer->second;
}
}
如前所述,线程局部变量通常默认初始化。然而,对于std::map
,不需要默认初始化。相反,每个线程本地变量都应该使用已经存在的映射进行初始化。这可以在初始化器内部指定,因此我们的pragma如下所示:
#pragma omp declare reduction(
mapAdd :
std::map<size_t, double> :
mapAdd(omp_out, omp_in)
)
initializer (omp_priv=omp_orig)
可用于上述CCD_ 17:
#pragma omp parallel for reduction(mapAdd : mymap)
for (size_t i = 0; i < typeDoubleVec.size(); ++i) {
mymap.at(typeDoubleVec[i].first) += typeDoubleVec[i].second;
}
不利的一面
这不适用于当前的clang编译器。它编译得很好,但它产生了一个分段错误,经过仔细研究,我发现这不是我的错,而是一个编译器错误,就像在当前的gcc和intel编译器上发现的一样。
此外,当在模板函数内部声明OpenMP缩减时,clang编译器会出现问题(未定义引用),因为它没有实例化OpenMP缩减内部所需的所有函数。
另请参阅以下问题:
- clang:(修复)使用自定义openmp减少的segfault:https://bugs.llvm.org/show_bug.cgi?id=44134
- clang:(修复)openmp的编译问题声明减少模板函数:https://bugs.llvm.org/show_bug.cgi?id=44133
在自定义缩减中使用lambdas
标准中没有规定在自定义OpenMP减少中使用lambdas(根据https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60228)。因此,我不建议这样做。然而,它确实适用于当前的英特尔编译器,并将适用于clang编译器的下一个版本(9.0.1或10)。GCC还不支持它(请参阅:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60228)。
使用关键部分
规避减少需求的一种方法是在非常基本的级别上复制它们,即,我们为每个线程创建一个本地副本,然后在关键部分内手动累积结果。这有一个优点,即它更容易阅读,但由于没有实现扇入,因此它可能比通过自定义减少的解决方案更慢。
采用这种方法的解决方案如下:
template <class vectype, class typevectype>
std::map<size_t, double> foo(const vectype &typeDoubleVec,
const typevectype &typevec) {
std::map<size_t, double> mymap;
// ensure that mymap has all elements of myvec.
for (auto &elem : typevec) {
Does anyone know how to speed this up with OpenMP? The way I see this working is that you need a custom OpenMP reduction?
mymap[elem];
}
#pragma omp parallel default(none) shared(typeDoubleVec, mymap)
{
// we create a local copy of mymap for each thread!
auto localmap = mymap;
#pragma omp for
// iterate over the vector and add them to the local map
for (size_t i = 0; i < typeDoubleVec.size(); ++i) {
localmap.at(typeDoubleVec[i].first) += typeDoubleVec[i].second;
}
#pragma omp critical
// sum up the local map to the global map using a critical section
mapAdd(mymap, localmap);
}
return mymap;
}
代码
使用自定义缩减的代码可以在https://gist.github.com/SteffenSeckler/404c214bcccf506d261264672e2b9341
使用关键部分的代码https://gist.github.com/SteffenSeckler/91943b881677f3cbe7b2d7d475471ee8
P.S
感谢您对将其拆分为Q+A 的反馈
- 存储在 std::map/std::set 中,与在存储所有数据后对向量进行排序
- 如何在<N>不发生内存泄漏的情况下同时(线程安全)填充 c++11 std::map<std::string,std::bitset*>?
- 无法在 std::map<std::string,std::shared_ptr 中设置值<class>>
- 如何在C++中迭代集合映射(std::map<std::set< char>, int >)?
- 如何初始化结构字段 std::map<std::string, std::string>称为参数
- issue with std::map std::find
- 为什么 std::map< std::map >不释放内存?
- C++ map<std::string> vs map<char *> 性能(我知道,"again?" )
- 确定运行时std::map/std::set的内存使用情况
- 映射上的模板参数无效 std::map< std::string, Stock*> &Stock
- 我的SFINAE检查std::map/std::vector有什么问题
- 使用 std::map<std::string, int> 计算表达式树
- 将数据从两种不同的数据结构插入 std::map <std::string, int> mymap 并通过套接字发送
- 类中的编译器错误,数据类型为 typedef map<std::string,std::p air<std::string,vector<int>>> MapPai
- C++ std::map<std::string, std::set<std::string>> .如何循环设置值?
- 如何填写和访问 std::map<std::p air<enum1, enum2>, funcPtr>?
- 二进制'<':找不到 map<std::string shared_ptr 的运算符<Foo>>
- 如何将 2 个字符* 数组直接映射到 std::map<std::string,std::string>
- C++ std::map<std::string, int> 获取键以特定字符串开头的值
- 从取消引用的迭代器返回 std::map<std::string, int> 时出现巨大的内存泄漏