简介
本文介绍openmp中reduction的进阶用法,针对于非内置数据类型的自定义reducion用法。从上一篇文章【学习openmp-reducion】已经了解了reduction并行优化处理,其中内容都是openmp-reduction的基础用法,针对c++原有的内置数据类型,适用于int、float等内置的数据类型并行reduction处理,对于自定义的数据类型并不适用。针对于此问题,openmp推出了自定义的数据类型的reduction操作(custom-reduction)。需要注意的是此功能在OpenMP 3.x以上版本才支持,所以在windows的mvsc编程环境下是不适用的。
语法
自定义的reduction操作实际上是openmp提供让用户自行声明reduction操作符,原本默认的reduction操作如+、-、min与max等openmp内置的reduction操作符是针对与c++缺省变量类型int、float等。对于自定义类型(比如一个struct或者class),+、-、min与max等操作就不再适用,需要自定义操作符并实现其具体的reduction操作,有一点c++的重载操作符或者重载函数的意思。自定义一个操作符的语法如下:
1
|
#pragma omp declare reduction (reduction-identifier : typename-list : combiner) [initializer-clause]
|
说明:
- reduction-identifier:归约标识符,相当于openmp自带的+,这里命名为MyAdd
- typename-list: 归约操作的数据类型,这里为MyClass
- combiner: 合并链接具体操作,+=为具体操作,omp_out与omp_in为固定的标识符
- initializer-clause: 归约操作的每个线程的初始值,比如求和操作时赋值100则等效于100xn(线程数)基础上再求和数组,定义格式为initializer(omp_priv=MyClass(100)) ,此项可以省略不写,初值会按类型的默认构造函数赋值
从上诉解释去看openmp内置的+-,min,max内置操作符,依据上述语法,其实是这么定义的
1
2
3
|
#pragma omp declare reduction (+ : T : omp_out += omp_in) omp_priv = 0
#pragma omp declare reduction (min : T : omp_out = std::min(omp_out,omp_in)) omp_priv = std::numeric_limits<T>::max()
//..
|
当然内部实际情况未必是这么实现的,我这里只是从语法规则角度去解析内置的reduction在语法规则下可以这么定义。
具体例子
接下来看具体例子,假如有以下这么一个自定义的数据类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
struct MyClass
{
int data;
MyClass(const int& data_): data(data_){}
MyClass& operator = (const MyClass& other)
{
return *this;
}
MyClass& operator += (const MyClass& other)
{
data += other.data;
return *this;
}
};
|
需要计算多个该类型数据的某个成员的总和时,openmp并行优化可以这么实现,首先需要定义该数据类型求和+=的openmp-reduction操作,依据上文所述的语法,MyClass的成员数据data+=操作可以定义为
1
2
3
4
|
#pragma omp declare reduction(MyAdd: MyClass: omp_out += omp_in) initializer(omp_priv=MyClass(0))
//初值赋予100, 与MyAdd区别就是每个线程的结果会多100,总结果会多出nx100,n为并行的线程数
#pragma omp declare reduction(MyAdd_with100: MyClass: omp_out += omp_in) initializer(omp_priv=MyClass(100))
|
定义好后,for循环优化使用方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
MyClass sum(0);
std::vector<MyClass> vec(100, 1);
#pragma omp parallel for reduction(MyAdd: sum)
for(int i = 0; i < vec.size(); ++i)
{
sum += vec[i];
}
std::cout << "custom reduction sum = " <<sum.data << std::endl;
sum.data = 0;
#pragma omp parallel for reduction(MyAdd_with100: sum)
for(int i = 0; i < vec.size(); ++i)
{
sum += vec[i];
std::cout << "initializer value = 100, threads count = "<< omp_get_num_threads() << std::endl;
}
|
在4线程的处理器平台运行下,结果为:
1
2
3
4
5
|
custom reduction sum = 100
initializer value = 100, threads count = 4
...
initializer value = 100, threads count = 4
custom reduction with 100 initializer sum = 500
|
两者相差400,符合预计的结果情况。接下来看另外一个例子,展示custom-reduction功能不仅可以实现简单的加减求和或者积指的归约,发挥好语法规则,还可以实现多个数组合并的操作。首先先看定义:
1
|
#pragma omp declare reduction(MyMerge: std::vector<MyClass>: omp_out.insert(omp_out.end(), omp_in.begin(), omp_in.end()))
|
注意看
1
|
omp_out.insert(omp_out.end(), omp_in.begin()
|
这段combiner语法的区别,这里近看代码就是将omp_in数组合并到omp_out的操作,实际上就是openmp最后多个线程结果出来后,其中一个线程(omp_in)结果合并到另一个线程(omp_out)中去,这个合并操作不仅限于加减乘除等简单运算,一般来说符合c++语法的都可以,掌握这一点就就可以灵活写出复杂类型的openmp-reduction并行优化。最后再看具体使用:
1
2
3
4
5
6
7
8
9
10
|
std::vector<MyClass> merge;
std::vector<std::vector<MyClass>> list(100, std::vector<MyClass>(5, 1));
#pragma omp parallel for reduction(MyMerge: merge)
for(int i = 0; i < list.size(); ++i)
{
merge.insert(merge.end(), list[i].begin(), list[i].end());
}
std::cout << "merge size = "<< merge.size() << std::endl;
|
执行后结果为:
1
|
custom reduction with 100 initializer sum = 500
|
与理论预计结果相符合。
本文练习代码已上传至github:https://github.com/mangosroom/learn-openmp/tree/main/custom_reduction
本文由芒果浩明发布,转载请注明出处。
本文链接:https://mangoroom.cn/parallel-programming/learn-openmp-custom-reduction.html