所以我遇到的问题大致如下:
我有一个相对较大的数据集,该数据集始终包含一对标识符和与之相关的值。因此,存在相对较少的不同但任意的标识符。
在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缩减功能?
答案 0 :(得分:0)
所以我自己遇到了答案: 有两种方法可以执行此操作,一种是使用自定义缩减,另一种是使用关键部分。我目前建议使用后者,主要是因为前者在当前的clang编译器(v9.0.0,修复已在trunk / master中)中损坏。
解决问题的第一种方法是使用OpenMP简化,通常看起来像这样:
// this does not work for maps!
#pragma omp parallel for reduction(+: mymap)
由于未为+
定义内置缩减std::map
,因此该代码片段无法编译。
相反,我们将必须定义自己的减少量。快速浏览一些OpenMP规范(https://www.openmp.org/spec-html/5.0/openmpsu107.html)会发现以下用于定义自定义归约的语法:
#pragma omp declare reduction(reduction-identifier : typename-list :
combiner) [initializer-clause] newline
std::map<size_t, double>
omp_in
和omp_out
作为输入,并将合并的结果存储在omp_out
中。对于简单的+
约简,这是omp_out += omp_in
。initializer(expression)
。如果缺少,则将默认初始化还原变量的线程本地副本。如果is存在,则表达式的格式必须为omp_priv = initializer
的{{1}}。它还可以使用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 omp declare reduction( \
mapAdd : \
std::map<size_t, double> : \
mapAdd(omp_out, omp_in) \
) \
initializer (omp_priv=omp_orig)
:
omp parallel for
这不适用于当前的clang编译器。 它可以很好地编译,但是却产生了分段错误,经过一番挖掘后,我发现这不是我的错,而是编译器错误,因为同一程序可以在当前的gcc和intel编译器上找到。
此外,当在模板函数内部声明OpenMP简化时,clang编译器会出现问题(未定义的引用),因为它没有实例化OpenMP简化内部所需的所有函数。
另请参阅以下问题:
标准中未指定在自定义OpenMP缩减中使用lambda(根据https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60228)。 因此,我不建议您这样做。 但是,它确实可以与当前的intel编译器一起使用,并且可以与clang编译器的下一个版本(9.0.1或10)一起使用。 GCC尚不支持它(请参阅:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60228)。
一种避免减少需求的方法是在非常基本的水平上重现它们,即,我们为每个线程创建一个本地副本,然后在关键部分内手动累积结果。这样做的好处是,它易于阅读,但比起通过自定义缩减的解决方案,它可能是一种较慢的解决方案,因为没有实现扇入。
采用这种方法的解决方案如下:
#pragma omp parallel for reduction(mapAdd : mymap)
for (size_t i = 0; i < typeDoubleVec.size(); ++i) {
mymap.at(typeDoubleVec[i].first) += typeDoubleVec[i].second;
}
使用自定义归约法的代码可以在https://gist.github.com/SteffenSeckler/404c214bcccf506d261264672e2b9341
中找到使用关键部分https://gist.github.com/SteffenSeckler/91943b881677f3cbe7b2d7d475471ee8
的代码感谢您反馈有关将其拆分为Q + A的反馈