oneAPI诞生背景
随着科学技术的飞速发展,高性能计算在人工智能、药物研制、智慧医疗、计算化学等领域发挥着日益重要的作用。然而随着后摩尔时代的到来,计算机系统结构进入了百花齐放百家争鸣的繁荣时期,CPU、GPU、FPGA和AI芯片等互为补充。硬件的多样性带来了软件设计与开发的复杂性,高性能计算并行程序的计算效率和在不同计算平台之间的可移植性日趋重要。为解决此问题,Intel推出了oneAPI。
oneAPI简介
Intel oneAPI 是一个跨行业、开放、基于标准的统一的编程模型,并为开发者提供了编写高性能并行应用程序的能力。它支持多种处理器架构,包括英特尔 CPU、GPU、FPGA 和其他加速器。英特尔oneAPI产品是英特尔基于oneAPI的实现,包括了oneAPI 标准组件如直接编程工具(Data Parallel C++)、含有一系列性能库的基于 API 的编程工具,以及先进的分析、调试工具等组件。开发人员从现在开始就可以在英特尔 DevCloud for oneAPI 上对基于多种英特尔架构(包括英特尔至强可扩展处理器、带集成显卡的英特尔酷睿处理器、英特尔 FPGA 如英特尔 Arria、Stratix 等)的代码和应用进行测试。
问题描述
桶排序是一个排序演算法,工作的原理是将阵列分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序演算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间
问题分析
- 扫描待排序序列,确定最大值 m a x max max与最小值 m i n min min
- 创建一个大小为 m a x − m i n + 1 max - min + 1 max−min+1的桶数组 b u c k e t bucket bucket,并将每个元素初始化为0
- 创建待排序序列,对于每个元素 x x x,将 b u c k e t [ x − m i n ] bucket[x - min] bucket[x−min]的指加1
- 遍历桶数组,对于每个非空桶 b u c k e t [ i ] bucket[i] bucket[i],输出 i + m i n i + min i+min,重复 b u c k e t [ i ] bucket[i] bucket[i]次
针对桶排序算法的串行实现,我们可以将其并行化来提高算法的性能。
代码实现
// 桶排序算法
void bucketSort(int arr[], int n) {
// 使用tbb::parallel_reduce算法并行计算最大值和最小值
int max_val = tbb::parallel_reduce(tbb::blocked_range<int>(0, n), arr[0],
[&](const tbb::blocked_range<int>& r, int init) {
for (int i = r.begin(); i < r.end(); i++) {
init = std::max(init, arr[i]);
}
return init;
},
[](int a, int b) {
return std::max(a, b);
});
int min_val = tbb::parallel_reduce(tbb::blocked_range<int>(0, n), arr[0],
[&](const tbb::blocked_range<int>& r, int init) {
for (int i = r.begin(); i < r.end(); i++) {
init = std::min(init, arr[i]);
}
return init;
},
[](int a, int b) {
return std::min(a, b);
});
// 创建一个大小为max-min+1的数组作为桶
int bucket_size = max_val - min_val + 1;
int* buckets = new int[bucket_size];
// 初始化桶中的元素为0
memset(buckets, 0, bucket_size * sizeof(int));
// 使用tbb::parallel_for算法对每个元素进行计数
tbb::parallel_for(tbb::blocked_range<int>(0, n), [&](const tbb::blocked_range<int>& r) {
for (int i = r.begin(); i < r.end(); i++) {
tbb::atomic<int>& b = buckets[arr[i] - min_val];
b++;
}
});
// 使用tbb::parallel_for算法遍历桶数组输出排序结果
tbb::parallel_for(tbb::blocked_range<int>(0, bucket_size), [&](const tbb::blocked_range<int>& r) {
// 定义一个共享变量index
static tbb::atomic<int> index;
// 初始化index为0
index = 0;
for (int i = r.begin(); i < r.end(); i++) {
for (int j = 0; j < buckets[i]; j++) {
// 获取index的当前值,并将其加1
int k = index.fetch_and_add(1);
// 将i+min_val赋值给排序数组的第k个元素
arr[k] = i + min_val;
}
}
});
// 释放桶数组的空间
delete[] buckets;
}
具体的并行化思路如下:
- 并行计算最大值和最小值:在串行桶排序算法中,需要先遍历整个数组来得到最大值和最小值。在并行版本中,可以使用TBB库提供的parallel_reduce算法对数组进行拆分,并行计算每个子区间内的最大值和最小值,然后将各个子区间的最大值和最小值进行合并即可得到整个数组的最大值和最小值,最后再根据它们的值来初始化桶数组
- 并行计数:在串行桶排序算法中,需要通过循环遍历整个数组,对每个元素进行计数。在并行版本中,可以使用TBB库提供的parallel_for算法对每个元素进行计数。具体来说,可以将原始数组按照线程数量均分成若干个子数组,让每个线程分别对自己负责的子数组进行计数,在操作完毕后将各个子数组的计数结果进行合并即可得到最终的计数结果
- 并行输出排序结果:在串行桶排序算法中,需要遍历桶数组来输出排序结果。在并行版本中,同样可以使用TBB库提供的parallel_for算法对桶数组进行并行遍历,并将排序结果存储在一个线程安全的容器中。在操作完毕后,我们可以使用std::copy函数将容器中的元素复制到原始数组中