INTEL oneAPI SYCL实现并行排序算法

INTEL oneAPI介绍

Intel oneAPI是Intel提供的统一编程模型和软件开发框架。 它旨在简化可充分利用英特尔各种硬件架构(包括 CPU、GPU 和 FPGA)的应用程序的开发。

oneAPI 提供了一组工具、库和框架,使开发人员能够编写跨不同硬件平台的高性能代码。 它支持多种编程语言,包括 C++、Fortran 和数据并行 C++ (DPC++)。 借助 oneAPI,开发人员可以使用熟悉的编程模型编写代码并针对不同的硬件架构,而无需对代码进行重大修改。

英特尔 oneAPI 的关键组件包括:

  1. oneAPI Base Toolkit:它包括编译器、库和工具,用于跨 CPU、GPU 和 FPGA 优化和并行化代码。
  2. oneAPI HPC Toolkit:它专注于高性能计算 (HPC) 工作负载,并为 HPC 开发提供额外的工具和库。
  3. oneAPI AI Analytics Toolkit:它专为人工智能 (AI) 和分析工作负载而设计,并为深度学习、机器学习和数据分析提供库和工具。
  4. oneAPI IoT Toolkit:专为物联网(IoT)应用量身定制,提供用于开发物联网解决方案的工具和库。

通过使用 oneAPI 编程模型和工具,开发人员可以编写可在不同类型的英特尔硬件上高效执行的代码,释放提高性能和能效的潜力。 它促进了一种统一且可扩展的软件开发方法,使开发人员能够利用英特尔硬件产品组合的全部功能。

使用步骤

有多种方式可以使用intel的oneAPI。登陆官方网站可以查看能够使用的CPU、GPU的种类和参数等等,同时也可以免费申请试用DEVCloud120天,直接在cloud上进行开发。

注册登陆后,可以看到intel提供的一系列toolkit。在这里选择直接用JupyterLab进行开发。

 

打开之后可以看到左侧有一系列的教程和例子,非常方便简洁,容易上手。

任务目标

描述

使用基于oneAPI的c++/SYCL实现一个高效的并行归并排序。需要考虑数据的分割和合并以及线程之间的协作。

问题分析

正常实现的归并排序:

void Merge(int a[], int tmp_a[], int first, int middle, int last) {
  int p1 = first;
  int p2 = middle;
  int p = first;
  while (p <= last) {
    if (p1 < middle && (p2 > last || a[p1] < a[p2])) {
      tmp_a[p++] = a[p1++];
    } else {
      tmp_a[p++] = a[p2++];
    }
  }
  for (int i = first; i <= last; ++i) {
    a[i] = tmp_a[i];
  }
}
void MergeSort(int a[], int tmp_a[], int first, int last) {
  if (first < last) {
    int middle = (first + last + 1) / 2;
    MergeSort(a, tmp_a, first, middle - 1);
    MergeSort(a, tmp_a, middle, last);
    Merge(a, tmp_a, first, middle, last);
  }
}

归并排序是一种经典的分治算法,它的基本思路是将一个大问题分成两个较小的子问题,解决子问题,然后将它们的解合并起来。归并排序的主要步骤包括分解(Divide)、解决(Conquer)和合并(Merge)。

  1. 分解(Divide): 将待排序的数组或列表一分为二,直到无法再分解为止。这一步是递归的,将数组分解成两个子数组,然后对每个子数组再进行分解,直到每个子数组只包含一个元素。

  2. 解决(Conquer): 对每个子问题进行递归求解。在归并排序中,递归的终止条件是数组只包含一个元素,因为一个元素的数组可以看作是已经排序好的。

  3. 合并(Merge): 将已排序的子数组合并成一个有序数组。这一步是将两个有序的子数组合并成一个更大的有序数组的过程。合并过程涉及到比较两个子数组中的元素,并将它们有序地合并到一个新的数组中。

整个过程可以通过递归来实现。当递归返回时,所有的子问题都已经被解决,而且每个子数组都是有序的。然后,通过合并有序的子数组,最终得到完全有序的数组。

归并排序的优点在于它是一种稳定的排序算法,而且在最坏情况下的时间复杂度是 O(n log n)。然而,归并排序需要额外的空间来存储临时数组,因此空间复杂度较高。在并行计算环境中,归并排序的分治性质使得它很适合并行化实现。

接下来并行的进行归并排序:

// Description:
// OpenMP Task version of merge_sort
void MergeSortOpenMP(int a[], int tmp_a[], int first, int last) {
  if (first < last) {
    int middle = (first + last + 1) / 2;  // = first + (last - first + 1) / 2;
    // Splits list a[first:last] into two halves (called sublists).
    // One is [first:middle-1], and another is [middle:last].
    // For the sake of performance, only when the list is big enough,
    // we create tasks with #pragma omp task.
    if (last - first < task_threshold) {
      MergeSort(a, tmp_a, first, middle - 1);
      MergeSort(a, tmp_a, middle, last);
    } else {
#pragma omp task
      MergeSortOpenMP(a, tmp_a, first, middle - 1);
#pragma omp task
      MergeSortOpenMP(a, tmp_a, middle, last);
#pragma omp taskwait
    }
    Merge(a, tmp_a, first, middle, last);
  }
}

该函数使用递归的分治策略,将数组划分为两个子数组,然后分别对这两个子数组进行排序。在划分和排序的过程中,利用OpenMP任务并行ism来实现并行处理。

具体步骤如下:

  1. 判断数组大小: 首先,通过比较 last - firsttask_threshold 来判断当前子数组的大小是否足够大,如果小于 task_threshold,则直接调用串行版本的 MergeSort 函数进行排序。这是为了避免创建过多的任务,以提高性能。

  2. 创建任务: 如果子数组大小大于等于 task_threshold,则通过 #pragma omp task 创建两个任务,分别对左半部分和右半部分进行排序。这样可以使排序过程更好地利用并行性。

  3. 等待任务完成: 利用 #pragma omp taskwait 来等待两个子任务的完成。这确保了在执行合并操作之前,左右两个子数组的排序已经完成。

  4. 合并: 最后,调用 Merge 函数对已排序的左右子数组进行合并。Merge 函数的目标是将已排序的两个子数组合并成一个有序数组。

  5. 运行代码,借助教程中的run.sh脚本,开启oneAPI环境并编译运行代码:

  6. #!/bin/bash
    source /opt/intel/oneapi/setvars.sh > /dev/null 2>&1
    /bin/echo "##" $(whoami) "is compiling Welcome Module-- 1 of 1 hello.cpp"
    icpx -fsycl src/hello.cpp -o src/hello
    if [ $? -eq 0 ]; then src/hello; fi
    

    执行:

    ! chmod 755 q; chmod 755 run_hello.sh;if [ -x "$(command -v qsub)" ]; then ./q run_hello.sh; else run_hello.sh; fi

    .sh文件

    #!/bin/bash
    source /opt/intel/oneapi/setvars.sh > /dev/null 2>&1
    /bin/echo "##" $(whoami) "is compiling Welcome Module-- 1 of 1 hello.cpp"
    icpx -fsycl src/hello.cpp -o src/hello
    if [ $? -eq 0 ]; then src/hello; fi

    首先调用setvars.sh脚本,它是oneAPI工具包的一部分,用于设置环境变量和路径,以便正确使用。icpx是DPC++编译器的命令,用于编译SYCL代码。-fsycl选项指定编译为SYCL代码。

    最后一行是一个条件语句,检查前一条命令的退出状态码。如果退出状态码为0(表示成功),则执行文件,即可运行编译生成的可执行文件。

运行结果:

对N = 100000000的数组进行排序:

学习心得:

 在本次试验中,通过实际操作深入理解了Intel oneAPI的核心概念和优势。oneAPI提供了一个统一的编程模型,用于跨足多种硬件(如CPU、GPU、FPGA)构建高性能应用,这一点在目前的高性能计算框架中难能可贵。

oneAPI强大之处在于其支持异构计算。在此作业中,我利用了oneAPI的SYCL扩展,它提供了一个标准的C++编程模型,简化了在不同类型的处理器上编程的复杂性。算法本身可以在不进行任何更改的情况下,支持异构计算,这种跨平台兼容性对于降本增效、优化性能至关重要。我使用了oneAPI中的并行编程工具来加速归并排序。这个过程让我体会到了并行化对于提高大规模计算任务效率的重要性,即便是免费提供的CPU资源,在应用并行加速后加速比也十分优秀。

最后,感谢Intel Devcloud提供的免费计算资源和环境,可以让我快速上手直接进行实验,无需进行任何环境配置,或购买任何特定硬件即可在真实的应用场景中进行实践。希望更多人也能体验到这一强大工具的便利和效能。

 参考文献:GitHub - oneapi-src/oneAPI-samples: Samples for Intel® oneAPI Toolkits

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ava实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),可运行高分资源 Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言中,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言中常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言中常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言中用于封装代码的单元,可以实现代码的复用和模块化。C语言中定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言中用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言中定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言中用于存储同类型数据的结构,可以通过索引访问和修改数组中的元素。字符串是C语言中用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言中用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言中通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习和实践打下坚实的基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值