在完成并行程序设计课程作业时,老师向我们介绍了一个平台——英特尔OneAPI。因此简单学习了一下OneAPI中DPC++的使用,并且用它完成了高斯消去并行化的作业,得到了不错的加速比。
英特尔OneAPI平台是一个用于让程序员只需编写一份代码,就可以在诸如CPU、GPU、FPGA等多种设备、平台上运行的异构计算平台。
在这次高斯消去作业中,我们使用到的是英特尔OneAPI中的DPC++。
DPC++是英特尔对SYCL编程模型的一个实现,它允许我们编写跨平台的并行代码。
介绍下高斯消去算法,这个算法是将任意矩阵化为上三角矩阵的一种算法。
如图所示,高斯消去的典型串行算法具体步骤如下:
N*N
的矩阵,设置变量i=0
代表当前正在处理的行i+1
到N
行的第i
个元素。使得矩阵的i
列中,i
行以下的元素全为0i
自增, 若i>N
,则消元结束。跟据这些步骤,我们可以用C++进行串行实现:
int mat[n][n]; // 待处理矩阵
for (int i = 0; i < n; i++) // 当前消去行
{
for (int j = i + 1; j < n; j++) // 被消去行
{
float div = mat[j][i] / mat[i][i]; // 将k行消除为0需要的比值
for (int k = i; k < n; k++) // 进行行变换
{
mat[j][k] -= mat[i][k] * div;
}
}
}
可以看到,代码中有三重循环,其中
i
行)便被用来消去其他行;i
行“下面”的所有矩阵行(第j行);j
行减去第i行,来实现将位于(j, i)
位置的矩阵元素置零的作用。很显然,这是一个复杂度为O(n^3)
的算法。那么如何将这个算法并行化呢?
首先我们需要决定进行并行化的循环层数。
第一重循环肯定不太好做并行化,因为这一循环存在数据依赖——执行顺序在后的循环步需要执行顺序在前的循环步提供数据。
第三重循环很容易并行化,但是矩阵一行也就数千个元素,进行任务划分和数据发送的时间可能都比计算要花的时间长了。
因此,我们的并行化应该做在第二重循环上。
在英特尔OneAPI DPC++中,将高斯消去的并行化的代码如下:
queue myQueue{ host_selector{} }; //创建队列
for (int i = 0; i < n; i++)
{
myQueue.parallel_for(range{(unsigned long)(n - (i + 1))}, [=](id<1> idx)
{
int j = idx[0] + i + 1; // 等同于for(int j=i+1; j
float div = new_mat[j][i] / new_mat[i][i];
for (int k = i; k < n; k++)
{
new_mat[j][k] -= new_mat[i][k] * div;
}
}).wait(); // wait() - 等待所有任务执行完成
}
虽然这还不是完整的代码,但是,是不是看上去很简单、优雅?甚至连代码都没多几行。
还有看不懂的写法?没事,我们一个一个来解释。
queue myQueue{ cpu_selector{} }; // 创建队列
首先是这一行,这行代码所做的事情便是创建一个队列(queue),在OneAPI中,队列是用来沟通主机(host)和运算设备(devices)的一种途径,我们可以通过向队列提交任务来让他们在运算设备(devices)上运行。
就比如说这一行中初始化队列使用到的cpu_selector{}
,便可以指定这个队列的运算设备为CPU,而我们接下来的算法也就自然会在CPU上运行。
myQueue.parallel_for(range{ n }, [=](id<1> idx){ do_something; }).wait();
parallel_for
函数正如其名,是并行化的for语句。
parallel_for
接受两个参数,第一个参数属于range类,比如这里的range{n}
便表示循环范围是[0, n)
,后面的idx
便是循环变量。而第二个参数则是一个lambda匿名函数,函数中就是给我们写for循环体的地方啦~
最后调用的wait()
函数是为了让程序阻塞,等待当前所有任务运行完毕后再开始下一次循环,避免出错。
int j = idx[0] + i + 1;
这一行确实写得有点让人疑惑,不过之所以这么写,是因为parallel_for
只能支持循环变量初值为0的情况的,所以需要我们手动加上一个偏移量。
queue myQueue{ gpu_selector{} }; // 创建队列
创建队列时,将cpu_selector{}
改为gpu_selector{}
可以通过gpu运算,不过我们的代码在gpu上运行的速度不怎样快。
本次作业全部是在英特尔DevCloud平台上完成的,这个平台不需要手动配置OneAPI的开发环境。
DevCloud平台的注册和使用可以参考这篇文章
作业中使用的完整代码已经上传到GitCode
通过这次对OneAPI的学习,我们深刻体会到了近年来并行程序设计工具的易用化。编写一次代码就能在不同的设备上做到并行运算,这是并行编程的一大进步。相信英特尔走出的这一步可以使得未来的高性能计算更能为人所用。