一、题目描述
描述
使用基于oneAPI
的
C++/SYCL
实现⼀个高效的并行归并排序。需要考虑数据的分割和合并以及线程之间的协作。
分析
&
示例
归并排序是⼀种分治算法,其基本原理是将待排序的数组分成两部分,分别对这两部分进行排序,然后将已排 序的子数组合并为⼀个有序数组。可考虑利用了异构并行计算的特点,将排序和合并操作分配给多个线程同时 执行,以提高排序效率。具体实现过程如下:
1.
将待排序的数组分割成多个较小的子数组,并将这些⼦数组分配给不同的线程块进行处理。
2.
每个线程块内部的线程协作完成子数组的局部排序。
3.
通过多次迭代,不断合并相邻的有序⼦数组,直到整个数组有序。
在实际实现中,归并排序可使用共享内存来加速排序过程。具体来说,可以利用共享内存来存储临时数据,减 少对全局内存的访问次数,从而提高排序的效率。另外,在合并操作中,需要考虑同步机制来保证多个线程之 间的数据⼀致性。
需要注意的是,在实际应用中,要考虑到数组大小、线程块大小、数据访问模式等因素,来设计合适的算法和参数设置,以充分利用目标计算硬件GPU的并行计算能力,提高排序的效率和性能。
二、解题思路
采用双调排序的思路。
由非严格增序列X和非严格将序列Y组成的序列称之为双调序列。
一个序列:a1,a2,a3,...,an是双调序列,必须满足以下条件:
(1)存在一个ak(1 <= k <= n),使得a1 >= ... >= ak <= ... <= an;
(2)序列能够满足循环移位条件
Batcher定理:
将任意一个长度为n的双调序列A分为等长的两半X、Y,将X中的元素与Y中的元素一一按照原序进行比较,也就是a [i] 与a [i + n/2] (i < n)进行比较,将较大者放入MAX序列中,较小者放入MIN序列,得到的MAX和MIN序列仍然是双调序列,并且MAX序列中任意一个元素是不小于MIN序列中任意一个元素的。我们可以把这个过程看作是一个逐步拆分序列的过程。
我们可以通过对一个双调序列进行多次自上而下的此类操作,直至它变为一个排好序的序列。
构造双调序列的方法与上述方法相反:我们首先设定子序列长度为1,将序列中的元素两两进行比较,第一趟构造一个长度为2的单调序列,序列的单调性依次为单调递减、单调递增这样往复下去,然后对长度为2的子序列进行两两归并,并再次对长度为4的序列中的元素进行排序,如此递归下去直到生成一个完整的双调序列。
三、代码
#include <math.h>
#include <iostream>
#include <string>
#include <optional>
#include <fstream>
#include "dpc_common.hpp"
using namespace sycl;
using namespace std;
#define DEBUG 0
void initializeArr(double* arr, string line) {
int index = 0;
istringstream iss(line);
double value;
while (iss >> value) {
arr[index++] = value;
}
}
int getSize(string line) {
int res = 0;
istringstream iss(line);
double value;
while (iss >> value) {
res ++;
}
return res;
}
int getN(int size) {
int res = 1;
int x = 2;
while(x<size) {
res += 1;
x *= 2;
}
return res;
}
int getNewSize(int n) {
int res = 1;
while(n--) {
res *= 2;
}
return res;
}
void ParallelBitonicSortBuffer(double data_arr[], int n, queue &q) {
// n: the exponent used to set the array size. Array size = power(2, n)
int size = pow(2, n);
buffer input(data_arr, range(size));
// step from 0, 1, 2, ...., n-1
for (int step = 0; step < n; step++) {
// for each step s, stage goes s, s-1, ..., 0
for (int stage = step; stage >= 0; stage--) {
int seq_len = pow(2, stage + 1);
// Constant used in the kernel: 2**(step-stage).
int two_power = 1 << (step - stage);
// Offload the work to kernel.
q.submit([&](auto &h) {
accessor a(input, h);
h.parallel_for(size, [=](id<1> i) {
// Assign the bitonic sequence number.
int seq_num = i / seq_len;
// Variable used to identified the swapped element.
int swapped_ele = -1;
// Because the elements in the first half in the bitonic
// sequence may swap with elements in the second half,
// only the first half of elements in each sequence is
// required (seq_len/2).
int h_len = seq_len / 2;
if (i < (seq_len * seq_num) + h_len) swapped_ele = i + h_len;
// Check whether increasing or decreasing order.
int odd = seq_num / two_power;
// Boolean variable used to determine "increasing" or
// "decreasing" order.
bool increasing = ((odd % 2) == 0);
// Swap the elements in the bitonic sequence if needed
if (swapped_ele != -1) {
if (((a[i] > a[swapped_ele]) && increasing) ||
((a[i] < a[swapped_ele]) && !increasing)) {
double temp = a[i];
a[i] = a[swapped_ele];
a[swapped_ele] = temp;
}
}
});
});
} // end stage
} // end step
}
// Function showing the array.
void DisplayArray(double a[], int array_size) {
for (int i = 0; i < array_size; ++i) cout << a[i] << " ";
cout << "\n";
}
int main(int argc, char *argv[]) {
int seed = 9, size;
string filename = "problem-2.txt";
ifstream infile(filename);
string line;
getline(infile, line);
size = getSize(line);
int n = getN(size);
int newSize = getNewSize(n);
cout << "\nArray size: " << size << "\n";
// Create queue on implementation-chosen default device.
queue q;
cout << "Device: " << q.get_device().get_info<info::device::name>() << "\n";
// Memory allocated for host access only.
double *data_arr = (double *)malloc(newSize * sizeof(double));
initializeArr(data_arr, line);
for(int i=size;i<newSize;i++) {
data_arr[i] = 100000000;
}
// Start timer
dpc_common::TimeInterval t_par1;
// Parallel sort using buffer allocation
ParallelBitonicSortBuffer(data_arr, n, q);
cout << "Kernel time using buffer allocation: " << t_par1.Elapsed()
<< " sec\n";
std::ofstream outputFile("problem-2-result.txt");
for (int i = 0; i < size - 1; i++) {
outputFile << std::fixed << data_arr[i];
outputFile << " ";
}
outputFile.close();
return 0;
}