基于oneAPI的C++/SYCL的并行排序算法

一、题目描述

描述
        使用基于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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值