文章目录
一、前言
编写⼀个基于oneAPI的C++/SYCL程序来执行矩阵乘法操作。需要考虑大尺寸矩阵的乘法操作以及不同线程之间的数据依赖关系。通常在实现矩阵乘法时,可以使用块矩阵乘法以及共享内存来提高计算效率。
二、代码解释
2.1 头文件包含
代码如下(示例):
#include <sycl/sycl.hpp>
#include <iostream>
#include <vector>
#include <random>
这里包含了SYCL、输入/输出操作、向量和随机数生成所需的头文件。
2.2 命名空间声明
代码如下(示例):
using namespace sycl;
using namespace std;
这些语句将sycl和std命名空间引入当前作用域。
2.3 CustomDeviceSelector 类
代码如下(示例):
class CustomDeviceSelector {
public:
CustomDeviceSelector(std::string vendorName) : vendorName_(vendorName){};
int operator()(const device &dev) {
// ...
};
private:
std::string vendorName_;
};
这个类定义了一个自定义设备选择器,根据设备是GPU还是CPU,以及设备名称是否包含指定的供应商名称,为设备分配一个评级。
2.4 矩阵初始化和随机数生成
代码如下(示例):
vector<vector<float>> matrix1(N, vector<float>(N));
vector<vector<float>> matrix2(N, vector<float>(N));
vector<vector<float>> matrix3(N, vector<float>(N));
// ...
创建了三个二维向量,表示大小为N x N的矩阵,并使用随机数填充矩阵 matrix1 和 matrix2。
2.5 创建缓冲区
代码如下(示例):
buffer<float, 2> Matrix1_buffer(reinterpret_cast<float*>(matrix1.data()), range<2>(N, N));
buffer<float, 2> Matrix2_buffer(reinterpret_cast<float*>(matrix2.data()), range<2>(N, N));
buffer<float, 2> Output_buffer(reinterpret_cast<float*>(matrix3.data()), range<2>(N, N));
创建了用于保存矩阵数据的 SYCL 缓冲区。
2.6 提交任务执行矩阵乘法
代码如下(示例):
queue q(selector);
q.submit([&](handler &h) {
// ...
});
创建了一个使用自定义设备选择器的 SYCL 队列,并向队列提交了一个执行矩阵乘法的任务。
2.7 为输出缓冲区创建访问器
代码如下(示例):
host_accessor h_a(Output_buffer, read_write);
创建了一个主机访问器,用于从输出缓冲区读取结果。
三.全部代码
代码如下(示例):
#include <sycl/sycl.hpp>
#include <iostream>
#include <vector>
#include <random>
using namespace sycl;
using namespace std;
class CustomDeviceSelector {
public:
CustomDeviceSelector(std::string vendorName) : vendorName_(vendorName){};
int operator()(const device &dev) {
int device_rating = 0;
if (dev.is_gpu() & (dev.get_info<info::device::name>().find(vendorName_) !=
std::string::npos))
device_rating = 3;
else if (dev.is_gpu())
device_rating = 2;
else if (dev.is_cpu())
device_rating = 1;
return device_rating;
};
private:
std::string vendorName_;
};
int main() {
const int N = 1024;
//二维数组
vector<vector<float>> matrix1(N, vector<float>(N));
vector<vector<float>> matrix2(N, vector<float>(N));
vector<vector<float>> matrix3(N, vector<float>(N));
//初始化
random_device rd;
mt19937 rng(rd());
uniform_int_distribution<int> dist(0, 2);
for (size_t i = 0; i < N; i++) {
for (size_t j = 0; j < N; j++) {
matrix1[i][j] = dist(rng);
matrix2[i][j] = dist(rng);
}
}
cout<<"\nmatrix1:\n";
for (size_t i=0;i<N;i++)
{
for (size_t j=0;j<N;j++){
cout<<matrix1[i][j]<<" ";
}
cout<<"\n";
}
cout<<"\nmatrix2:\n";
for (size_t i=0;i<N;i++)
{
for (size_t j=0;j<N;j++){
cout<<matrix2[i][j]<<" ";
}
cout<<"\n";
}
//Create buffers
buffer<float, 2> Matrix1_buffer(reinterpret_cast<float*>(matrix1.data()), range<2>(N, N));
buffer<float, 2> Matrix2_buffer(reinterpret_cast<float*>(matrix2.data()), range<2>(N, N));
buffer<float, 2> Output_buffer(reinterpret_cast<float*>(matrix3.data()), range<2>(N, N));
//Choose device
std::string vendor_name = "Intel";
CustomDeviceSelector selector(vendor_name);
//Submit task to multiply matrices
queue q(selector);
q.submit([&](handler &h) {
//# Create accessors for buffers
accessor M1 (Matrix1_buffer,h,read_only);
accessor M2 (Matrix2_buffer,h,read_only);
accessor M3 (Output_buffer,h,write_only);
h.parallel_for(nd_range<2>({N, N}, {64, 64}), [=](nd_item<2> item) {
//# Multiplication
size_t row = item.get_global_id(0);
size_t col = item.get_global_id(1);
for (size_t k = 0; k < N; ++k) {
M3[row][col] += M1[row][k] * M2[k][col];
}
});
});
//Create accessor
host_accessor h_a(Output_buffer, read_write);
cout << "\nmatrix3:\n";
for (size_t i = 0; i < N; i++) {
for (size_t j = 0; j < N; j++) {
cout << matrix3[i][j] << " ";
}
cout << "\n";
}
return 0;
}
四.学习心得
oneAPI强大之处在于其支持异构计算。在此作业中,我利用了oneAPI的SYCL扩展,它提供了一个标准的C++编程模型,简化了在不同类型的处理器上编程的复杂性,且算法本身可以在不进行任何更改的情况下,支持异构计算,这种跨平台兼容性对于降本增效、优化性能至关重要。我使用了oneAPI中的并行编程工具来加速矩阵乘法。这个过程让我体会到了并行化对于提高大规模计算任务效率的重要性,