使用oneAPI和SYCL实现并行归并排序

使用oneAPI和SYCL实现并行归并排序

一、问题陈述

​ 在处理大量数据时,排序算法的效率至关重要。归并排序是一种有效的、基于比较的排序算法,它使用分治策略。然而,传统的归并排序算法是串行的,当处理大量数据时,可能会遇到性能瓶颈。因此,我们需要寻找一种方法,将归并排序算法并行化,以提高其处理大量数据的能力。

二、项目简介

​ 本项目的目标是使用oneAPISYCL来实现并行归并排序算法。我们将使用SYCL的并行计算功能,将排序和合并操作分配给多个线程同时执行。这样可以充分利用异构计算设备的并行计算能力,提高排序效率。
​ 分析一下、归并排序是⼀种分治算法,其基本原理是将待排序的数组分成两部分,分别对这两部分进行排序,然后将已排序的子数组合并为⼀个有序数组。可考虑利用了异构并行计算的特点,将排序和合并操作分配给多个线程同时执行,以提高排序效率。具体实现过程如下:

  1. 将待排序的数组分割成多个较小的子数组,并将这些⼦数组分配给不同的线程块进行处理。
  2. 每个线程块内部的线程协作完成子数组的局部排序。
  3. 通过多次迭代,不断合并相邻的有序⼦数组,直到整个数组有序。

三、采用技术栈和主要方案🏵️

​ 本项目使用了oneAPISYCL作为主要的技术栈。oneAPI是一种跨供应商的开放编程模型,用于简化异构计算。SYCL是一种基于C++的异构并行编程模型,它是oneAPI的一部分。

​ 我们的主要方案是将归并排序算法并行化。首先,我们将数据分割成更小的部分,然后对每个部分进行排序,最后将排序后的部分合并。这个过程是递归的,直到数据不能再分割。在合并阶段,我们使用并行算法来提高效率。我们使用了SYCL的buffer和queue,以及parallel_for来实现并行计算。

下面是代码的详细解释:

void mergeSort(queue &q, buffer<int, 1> &buf, int left, int right) {
  if (right - left < 2) return;
  int mid = (left + right) / 2;

mergeSort函数是归并排序的主要部分。它接受一个队列q,一个缓冲区buf,以及两个整数leftright作为参数。队列q是用来提交任务的,缓冲区buf是存储数据的,leftright定义了要排序的数据的范围。

  // 分割数据
  mergeSort(q, buf, left, mid);
  mergeSort(q, buf, mid, right);

这部分代码将数据分割成两部分,并对每一部分进行排序。这是通过递归调用mergeSort函数实现的。

  // 合并数据
  buffer<int, 1> tmp_buf(right - left);
  q.submit(& {
    auto buf_acc = buf.get_access<access::mode::read_write>(h);
    auto tmp_acc = tmp_buf.get_access<access::mode::discard_write>(h);
    h.parallel_for(nd_range<1>(range<1>(right - left), range<1>(64)), = {
      int i = left + item.get_global_id(0);
      int j = mid + item.get_global_id(0) - (mid - left);
      if (i < mid && (j >= right || buf_acc[i] <= buf_acc[j])) {
        tmp_acc[item.get_global_id(0)] = buf_acc[i];
      } else {
        tmp_acc[item.get_global_id(0)] = buf_acc[j];
      }
    });
  });

这部分代码是合并操作的主要部分。首先,创建一个临时缓冲区tmp_buf来存储排序后的数据。然后,提交一个任务到队列q。在这个任务中,首先获取buftmp_buf的访问器buf_acctmp_acc,然后使用parallel_for函数来并行地执行合并操作。nd_range<1>(range<1>(right - left), range<1>(64))指定了工作项的总数和每个工作组的大小。nd_item<1> item是一个表示单个工作项的对象,它提供了一些用于并行编程的功能,比如获取工作项的全局和局部ID。

  // 将排序后的数据复制回原数组
  q.submit(& {
    auto buf_acc = buf.get_access<access::mode::discard_write>(h);
    auto tmp_acc = tmp_buf.get_access<access::mode::read>(h);
    h.parallel_for(nd_range<1>(range<1>(right - left), range<1>(64)), = {
      buf_acc[left + item.get_global_id(0)] = tmp_acc[item.get_global_id(0)];
    });
  });
}

这部分代码将排序后的数据从临时缓冲区tmp_buf复制回原缓冲区buf。这是通过提交另一个任务到队列q来实现的。在这个任务中,首先获取buftmp_buf的访问器buf_acctmp_acc,然后使用parallel_for函数来并行地执行复制操作。

int main() {
  queue q;
  std::vector<int> data = {5, 3, 1, 4, 2};
  buffer<int, 1> buf(data.data(), data.size());

  mergeSort(q, buf, 0, data.size());

  auto host_buf = buf.get_access<access::mode::read>();
  for (int i = 0; i < data.size(); ++i) {
    std::cout << host_buf[i] << " ";
  }
  std::cout << std::endl;

  return 0;
}

main函数是程序的入口点。首先,创建一个队列q和一个缓冲区buf。然后,调用mergeSort函数来对数据进行排序。最后,将排序后的数据打印出来。

完整代码如下:

#include <CL/sycl.hpp>
#include <algorithm>
#include <iostream>
#include <vector>

using namespace sycl;

void mergeSort(queue &q, buffer<int, 1> &buf, int left, int right) {
  if (right - left < 2) return;
  int mid = (left + right) / 2;

  // 分割数据
  mergeSort(q, buf, left, mid);
  mergeSort(q, buf, mid, right);

  // 合并数据
  buffer<int, 1> tmp_buf(right - left);
  q.submit(& {
    auto buf_acc = buf.get_access<access::mode::read_write>(h);
    auto tmp_acc = tmp_buf.get_access<access::mode::discard_write>(h);
    h.parallel_for(nd_range<1>(range<1>(right - left), range<1>(64)), = { // 优化点:使用nd_range来指定工作组大小
      int i = left + item.get_global_id(0);
      int j = mid + item.get_global_id(0) - (mid - left);
      if (i < mid && (j >= right || buf_acc[i] <= buf_acc[j])) {
        tmp_acc[item.get_global_id(0)] = buf_acc[i];
      } else {
        tmp_acc[item.get_global_id(0)] = buf_acc[j];
      }
    });
  });

  // 将排序后的数据复制回原数组
  q.submit(& {
    auto buf_acc = buf.get_access<access::mode::discard_write>(h);
    auto tmp_acc = tmp_buf.get_access<access::mode::read>(h);
    h.parallel_for(nd_range<1>(range<1>(right - left), range<1>(64)), = {
      buf_acc[left + item.get_global_id(0)] = tmp_acc[item.get_global_id(0)];
    });
  });
}

int main() {
  queue q;
  std::vector<int> data = {5, 3, 1, 4, 2};
  buffer<int, 1> buf(data.data(), data.size());

  mergeSort(q, buf, 0, data.size());

  auto host_buf = buf.get_access<access::mode::read>();
  for (int i = 0; i < data.size(); ++i) {
    std::cout << host_buf[i] << " ";
  }
  std::cout << std::endl;

  return 0;
}

四、收获🍎

通过这个项目,我们学习了如何使用oneAPISYCL来实现并行归并排序。我们理解了并行计算的概念,掌握了SYCL编程模型,理解了异构计算的优势,学习了如何优化并行算法,以及理解了归并排序算法。这些都是在进行高性能计算和并行编程时非常重要的知识点。希望这篇文章能帮助你在学习和使用oneAPI时有所收获。😊

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值