INTEL oneAPI介绍
Intel oneAPI是Intel提供的统一编程模型和软件开发框架。 它旨在简化可充分利用英特尔各种硬件架构(包括 CPU、GPU 和 FPGA)的应用程序的开发。
oneAPI 提供了一组工具、库和框架,使开发人员能够编写跨不同硬件平台的高性能代码。 它支持多种编程语言,包括 C++、Fortran 和数据并行 C++ (DPC++)。 借助 oneAPI,开发人员可以使用熟悉的编程模型编写代码并针对不同的硬件架构,而无需对代码进行重大修改。
英特尔 oneAPI 的关键组件包括:
- oneAPI Base Toolkit:它包括编译器、库和工具,用于跨 CPU、GPU 和 FPGA 优化和并行化代码。
- oneAPI HPC Toolkit:它专注于高性能计算 (HPC) 工作负载,并为 HPC 开发提供额外的工具和库。
- oneAPI AI Analytics Toolkit:它专为人工智能 (AI) 和分析工作负载而设计,并为深度学习、机器学习和数据分析提供库和工具。
- 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)。
-
分解(Divide): 将待排序的数组或列表一分为二,直到无法再分解为止。这一步是递归的,将数组分解成两个子数组,然后对每个子数组再进行分解,直到每个子数组只包含一个元素。
-
解决(Conquer): 对每个子问题进行递归求解。在归并排序中,递归的终止条件是数组只包含一个元素,因为一个元素的数组可以看作是已经排序好的。
-
合并(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来实现并行处理。
具体步骤如下:
-
判断数组大小: 首先,通过比较
last - first
与task_threshold
来判断当前子数组的大小是否足够大,如果小于task_threshold
,则直接调用串行版本的MergeSort
函数进行排序。这是为了避免创建过多的任务,以提高性能。 -
创建任务: 如果子数组大小大于等于
task_threshold
,则通过#pragma omp task
创建两个任务,分别对左半部分和右半部分进行排序。这样可以使排序过程更好地利用并行性。 -
等待任务完成: 利用
#pragma omp taskwait
来等待两个子任务的完成。这确保了在执行合并操作之前,左右两个子数组的排序已经完成。 -
合并: 最后,调用
Merge
函数对已排序的左右子数组进行合并。Merge
函数的目标是将已排序的两个子数组合并成一个有序数组。 -
运行代码,借助教程中的run.sh脚本,开启oneAPI环境并编译运行代码:
-
#!/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