前言
英特尔oneAPI工具集是一套专注于多种硬件架构的开发工具,包括SYCL编程模型和DPC++语言。该工具集提供了统一的编程模型和跨平台的开发环境,使得开发者能够更轻松地利用多核CPU、GPU、FPGA等异构处理器的计算能力,从而在多种硬件架构上实现高性能的并行计算。以下是使用英特尔oneAPI工具集实现并行计算的三个简单示例的代码:并行矩阵乘法、并行归并排序算法、并行图像卷积算法。
一、并行矩阵乘法
#include <CL/sycl.hpp>
constexpr int N = 16;
using namespace sycl;
class IntelGPUSelector : public device_selector {
public:
int operator()(const device& Device) const override {
const std::string DeviceName = Device.get_info<info::device::name>();
const std::string DeviceVendor = Device.get_info<info::device::vendor>();
return Device.is_gpu() && (DeviceName.find("Intel") != std::string::npos) ? 100 : 0;
}
};
int main() {
IntelGPUSelector d;
queue q(d);
sycl::range<2> size(N, N);
std::vector<vector<float>> v1(N, vector<int>(N, 2.0f));
std::vector<vector<float>> v2(N, vector<int>(N, 3.0f));
std::vector<vector<float>> v3(N, vector<int>(N, 0.0f));
buffer<float, 2> buf1(v1.data(), size);
buffer<float, 2> buf2(v2.data(), size);
buffer<float, 2> buf3(v3.data(), size);
q.submit([&](handler& h) {
auto acc1 = buf1.get_access<sycl::access::mode::read>(h);
auto acc2 = buf2.get_access<sycl::access::mode::read>(h);
auto acc3 = buf3.get_access<sycl::access::mode::write>(h);
h.parallel_for<class MatrixMultiply>(size, [=](sycl::id<2> idx) {
float sum = 0.0f;
for (int i = 0; i < N; i++) {
sum += acc1[idx[0]][i] * acc2[i][idx[1]];
}
acc3[idx] = sum;
});
});
q.wait();
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
std::cout << v3[i * N + j] << " ";
}
std::cout << std::endl;
}
return 0;
}
- 设备选择:定义了一个 IntelGPUSelector 类,用于选择 Intel GPU 设备。
- 主函数:创建了一个 IntelGPUSelector 实例,并用它获取了一个选择设备的队列。
定义了一个二维范围 size,其维度为 N x N。 - 矩阵初始化:初始化了三个二维向量(v1、v2、v3),它们分别代表了 N x N 的矩阵。从这些向量创建了缓冲区(buf1、buf2、buf3)。
- 并行矩阵乘法:使用名为 MatrixMultiply 的 SYCL 队列提交了一个 parallel_for 内核。内核执行矩阵 v1 和 v2 的乘法,并将结果存储在 v3 中。
- 等待队列执行:程序等待 SYCL 队列完成执行。
- 打印结果:最终矩阵 v3 的结果被打印到控制台上。
二、并行归并排序算法
#include <CL/sycl.hpp>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace sycl;
void serialMergeSort(std::vector<int>& data) {
std::sort(data.begin(), data.end());
}
void mergeSequences(queue& deviceQueue, buffer<int, 1>& bufferData, int start, int middle, int end) {
std::vector<int> tempBuffer(end - start + 1);
buffer<int, 1> tempBuf(tempBuffer.data(), tempBuffer.size());
deviceQueue.submit([&](handler& cgh) {
auto dataAcc = bufferData.get_access<access::mode::read_write>(cgh);
auto tempAcc = tempBuf.get_access<access::mode::write>(cgh);
cgh.parallel_for(range<1>(end - start + 1), [=](id<1> idx) {
int leftIndex = start;
int rightIndex = middle + 1;
int tempIndex = idx[0];
if (leftIndex <= middle && (rightIndex > end || dataAcc[leftIndex] < dataAcc[rightIndex])) {
tempAcc[tempIndex] = dataAcc[leftIndex++];
} else {
tempAcc[tempIndex] = dataAcc[rightIndex++];
}
});
});
deviceQueue.wait();
deviceQueue.submit([&](handler& cgh) {
auto dataAcc = bufferData.get_access<access::mode::write>(cgh);
auto tempAcc = tempBuf.get_access<access::mode::read>(cgh);
cgh.parallel_for(range<1>(end - start + 1), [=](id<1> idx) {
dataAcc[start + idx[0]] = tempAcc[idx[0]];
});
});
deviceQueue.wait();
}
void parallelMergeSort(queue& deviceQueue, buffer<int, 1>& bufferData, int start, int end, int maxDepth) {
if (start < end) {
if (maxDepth <= 0) {
auto hostData = bufferData.get_access<access::mode::read_write>();
std::vector<int> localData(hostData.get_pointer() + start, hostData.get_pointer() + end + 1);
serialMergeSort(localData);
for (int i = start; i <= end; ++i) {
hostData[i] = localData[i - start];
}
} else {
int middle = (start + end) / 2;
parallelMergeSort(deviceQueue, bufferData, start, middle, maxDepth - 1);
parallelMergeSort(deviceQueue, bufferData, middle + 1, end, maxDepth - 1);
mergeSequences(deviceQueue, bufferData, start, middle, end);
}
}
}
int main() {
const int numElements = 1 << 20;
std::vector<int> inputData(numElements);
std::generate(inputData.begin(), inputData.end(), [] { return std::rand() % 100; });
queue deviceQueue;
buffer<int, 1> dataBuf(inputData.data(), range<1>(numElements));
parallelMergeSort(deviceQueue, dataBuf, 0, numElements - 1, 4);
auto sortedBuffer = dataBuf.get_access<access::mode::read>();
for (int i = 0; i < numElements; ++i) {
if (i > 5000 && i < 5100) {
std::cout << sortedBuffer[i] << " ";
}
}
std::cout << "\n";
bool isSorted = std::is_sorted(sortedBuffer.get_pointer(), sortedBuffer.get_pointer() + numElements);
std::cout << "Array is sorted: " << (isSorted ? "Yes" : "No") << std::endl;
return 0;
}
- 头文件和命名空间:引入 SYCL 头文件和其他必要的 C++ 头文件。使用 sycl 命名空间。
- 串行归并排序:serialMergeSort 函数使用 C++ 标准库中的 std::sort 对输入向量进行排序。
- 归并操作:mergeSequences 函数在设备上执行并行归并操作。使用两个缓冲区,一个用于输入数据 (bufferData),另一个用于暂存中间结果 (tempBuf)。使用两个访问器分别访问输入和输出数据。通过 parallel_for 在设备上执行并行归并操作。
- 并行归并排序:parallelMergeSort 函数使用递归方式实现归并排序。当递归深度小于等于零时,使用 serialMergeSort 在主机上执行串行排序。否则,将问题划分为两个子问题,分别在设备上递归调用 parallelMergeSort,然后执行归并操作。
- 主函数:生成一个包含随机整数的输入向量。创建 SYCL 队列和输入缓冲区。调用 parallelMergeSort 在设备上执行排序。获取排序后的数据,并在指定范围内输出一部分结果。检查排序结果是否正确。
三、并行图像卷积算法
#include <CL/sycl.hpp>
#include <vector>
#include <iostream>
using namespace sycl;
void convolve2D(const std::vector<float>& inputImage, const std::vector<float>& kernel,
std::vector<float>& outputImage, int imageSize, int kernelSize, queue& q) {
buffer<float, 2> bufferInput(inputImage.data(), range<2>(imageSize, imageSize));
buffer<float, 2> bufferKernel(kernel.data(), range<2>(kernelSize, kernelSize));
buffer<float, 2> bufferOutput(outputImage.data(), range<2>(imageSize, imageSize));
q.submit([&](handler& cgh) {
auto accInput = bufferInput.get_access<access::mode::read>(cgh);
auto accKernel = bufferKernel.get_access<access::mode::read>(cgh);
auto accOutput = bufferOutput.get_access<access::mode::write>(cgh);
cgh.parallel_for(range<2>(imageSize - kernelSize + 1, imageSize - kernelSize + 1), [=](id<2> idx) {
int x = idx[0];
int y = idx[1];
float sum = 0.0f;
for (int i = 0; i < kernelSize; ++i) {
for (int j = 0; j < kernelSize; ++j) {
sum += accInput[x + i][y + j] * accKernel[i][j];
}
}
accOutput[x + kernelSize/2][y + kernelSize/2] = sum;
});
}).wait();
}
int main() {
const int imageSize = 1024;
const int kernelSize = 3;
std::vector<float> inputImage(imageSize * imageSize, 1.0f);
std::vector<float> kernel = {
0.0625f, 0.125f, 0.0625f,
0.125f, 0.25f, 0.125f,
0.0625f, 0.125f, 0.0625f
};
std::vector<float> outputImage(imageSize * imageSize, 0.0f);
queue q;
convolve2D(inputImage, kernel, outputImage, imageSize, kernelSize, q);
for (int i = 0; i < 10; ++i) {
std::cout << "Output[" << i << "] = " << outputImage[i] << std::endl;
}
return 0;
}
}
- 头文件和命名空间:引入 SYCL 头文件和其他必要的 C++ 头文件。使用 sycl 命名空间。
- 卷积函数 convolve2D:创建包含输入图像、卷积核和输出图像的缓冲区。使用 SYCL 队列提交一个 kernel 函数,该函数在设备上并行执行卷积操作。获取缓冲区的访问器,以便在 kernel 函数中访问数据。使用 parallel_for 在设备上迭代执行卷积操作。将卷积结果写入输出图像缓冲区。
- 主函数 main:定义图像大小、卷积核大小等参数。创建输入图像、卷积核和输出图像的向量。创建 SYCL 队列。调用 convolve2D 函数在设备上执行二维卷积操作。打印输出图像中的前 10 个元素。
总结
通过这次作业,我深刻体会到并行计算的重要性以及 oneAPI 工具集为实现并行计算提供的便利。在当今高性能计算领域,充分利用异构处理器的并行计算能力对于解决复杂的科学和工程问题至关重要。oneAPI 工具集为开发者提供了一个强大的工具箱,使得我们能够更加高效地利用不同硬件架构,从而取得更好的性能和效果。