SYCL和OpenCL






4.11/5 (2投票s)
SYCL和OpenCL之间的区别和相似之处
您想编写能够利用专用硬件的高效、可扩展的软件吗?如果是,您应该考虑使用 Open Computing Language (OpenCL) 和 SYCL 进行跨架构编程和异构计算。用于跨架构编程和异构计算。
跨架构编程允许您的代码在不同类型的硬件上运行,例如中央处理单元 (CPU)、图形处理单元 (GPU) 和现场可编程门阵列 (FPGA)。您可以利用这些硬件架构的独特优势来提高应用程序性能。
异构计算与跨架构编程密切相关,它指的是在单个应用程序中使用各种专用硬件。两者都允许用户开发高效且可扩展的工作负载,并且可以通过 OpenCL 编程框架由开发人员访问。SYCL(发音为“sickle”)作为 C++ 抽象层,使队列的卸载过程和跨架构并行性对 C++ 应用程序开发人员来说更加容易。SYCL 利用 OpenCL 来实现这一目标,但也可以与其他此类解决方案接口,例如 oneAPI Level Zero。
通过 OpenCL 框架,您可以编写在异构系统中的各种可用硬件上运行的程序。例如,机器学习算法可以使用 CPU 进行数据处理,使用 GPU 进行复杂的矩阵计算。将部分应用程序工作负载卸载到多种硬件类型可以提高计算效率。
为了实现这一点,OpenCL 提供了一个运行时环境,用于协调多个 CPU 和 GPU 之间的执行和数据传输。然而,OpenCL 是一个低级的、不可组合的、基于 C 的接口。因此,它取得了显著的成功和普及,但未能获得普遍的供应商支持或更高级别的 C++ 接口。
SYCL 是面向开发人员的高级 C++ 抽象层。至关重要的是,它是可组合的,因此您可以自由地在应用程序及其关联库中使用它,没有任何限制。到目前为止,SYCL 在 CPU、GPU 和 FPGA 上都得到了性能支持。得益于 LLVM 开源开发的强大功能,即使是 NVIDIA GPU 也得到了很好的支持——这是 OpenCL 未能克服的弱点。使用 SYCL,编写跨平台程序非常简单、直观且得到良好支持。
在本文中,我们将比较 SYCL 和 OpenCL,介绍一些具体的历史用例,并通过一个执行向量加法运算的简单代码示例演示两者之间的区别。
比较 SYCL 和 OpenCL
OpenCL 的开发目的是允许应用程序利用 GPU 的并行计算能力。它已成为异构系统并行编程的开放标准,并提供 C/C++ 中的低级编程模型。通过 OpenCL,您可以编写高效的应用程序在 CPU、GPU 和其他专用处理器(如 FPGA)上运行。
Apple 开发了 OpenCL 规范。OpenCL 1.0 与 Mac OS X Snow Leopard 一起发布。2009 年,OpenCL 成为由非营利性技术联盟 Khronos Group 维护的开放标准。
包括 Intel、AMD 和 NVIDIA 在内的许多公司都采用了 OpenCL,它们都在 2011 年发布了第一版 OpenCL SDK。虽然 Apple 不再推广 OpenCL,转而青睐其用于 GPU 编程的 Metal 接口,但 NVIDIA 已承诺支持最新的 OpenCL 3.0 规范。
尽管 NVIDIA 的 CUDA(Compute Unified Device Architecture)编程模型在利用其 GPU 时可提供顶级性能,但值得称赞的是,它们也为 OpenCL 提供了高质量的支持。看到 NVIDIA 对 CUDA 和 OpenCL 的投入,确保开发人员能够访问多种 GPU 计算编程模型,这非常好。例如,流行的深度学习框架 PyTorch 同时拥有 CUDA 和 OpenCL 后端。
通过支持 OpenCL,NVIDIA 使开发人员更容易将代码移植到 NVIDIA GPU,并允许他们在需要时使用其他硬件平台。
多年来,OpenCL 在众多领域实现了更强大的计算能力
- 在医学领域,它加速了医学影像的处理。
- 在汽车行业,它提高了自动驾驶系统的性能,使计算机视觉算法和传感器融合更高效。
- 在游戏行业,它被用于加速渲染管线。
- 在金融行业,一些银行使用它来加速蒙特卡洛模拟以进行股票价格预测。
虽然 OpenCL 在低级硬件访问方面仍然很重要,但它在广泛的应用使用方面存在局限性。SYCL 解决了这些局限性。
SYCL 于 2015 年作为 OpenCL 的更高级别抽象层出现。SYCL 提供了一个基于现代 C++ 的更简单、更直观的编程模型,使其对熟悉 C++ 和标准模板库 (STL) 的开发人员更具吸引力。在 SYCL 中采用 C++ 也有助于将新的 SYCL 代码集成到现有的 C++ 项目中。使用模板和现代 C++,包括跨主机-设备边界,是一个很大的优势。SYCL 中也存在对更高级别抽象(例如,规约)的支持。
SYCL 提供更广泛的设备支持,因为 SYCL 可以构建在许多不同的后端(不仅仅是 OpenCL)之上,以触及各种硬件。例如,Intel 的 SYCL CPU 实现使用 OpenCL 后端,而 Intel® GPU 计算运行时实现则利用 Level Zero 实现更细粒度的通用共享内存控制。
与 OpenCL 相比,SYCL 的一个特别吸引人的优势是其单一源编程模型,它将主机代码和设备代码统一在一个源文件中,从而更容易编写、理解和维护代码。SYCL 还提供了诸如访问器和缓冲区之类的抽象,它们简化了异构体系结构的编程并减少了重复代码。
语法差异:SYCL 和 OpenCL
为了理解 SYCL 和 OpenCL 之间的语法差异,让我们来看一下在两种编程模型中执行简单向量加法代码的实现。此内核接收向量 a
和 b
以将它们的每个元素相加到向量 c 中。这是使用专用硬件(如 GPU)并行完成的,因此我们将同时添加元素 a[0]
和 b[0]
以及 a[1]
和 b[1]
。
下面是 OpenCL 中的实现
#include <iostream>
#include <vector>
#include <CL/cl.hpp>
int main() {
const size_t n = 100;
std::vector<float> a(n, 1.0f);
std::vector<float> b(n, 2.0f);
std::vector<float> c(n, 0.0f);
// Get available platforms
std::vector<cl::Platform> platforms;
cl::Platform::get(&platforms);
// Get available devices
std::vector<cl::Device> devices;
platforms[0].getDevices(CL_DEVICE_TYPE_ALL, &devices);
// Create a context and command queue
cl::Context context(devices);
cl::CommandQueue queue(context, devices[0]);
// Create buffers
cl::Buffer a_buf(context, CL_MEM_READ_ONLY, n * sizeof(float));
cl::Buffer b_buf(context, CL_MEM_READ_ONLY, n * sizeof(float));
cl::Buffer c_buf(context, CL_MEM_WRITE_ONLY, n * sizeof(float));
// Copy data to buffers
queue.enqueueWriteBuffer(a_buf, CL_TRUE, 0, n * sizeof(float), a.data());
queue.enqueueWriteBuffer(b_buf, CL_TRUE, 0, n * sizeof(float), b.data());
// Create and build program
std::string kernel_code = R"(
__kernel void add_vectors(__global const float* a,
__global const float* b,
__global float* c) {
const int i = get_global_id(0);
c[i] = a[i] + b[i];
}
)";
cl::Program program(context, kernel_code);
program.build(devices);
// Create kernel
cl::Kernel kernel(program, "add_vectors");
// Set kernel arguments
kernel.setArg(0, a_buf);
kernel.setArg(1, b_buf);
kernel.setArg(2, c_buf);
// Enqueue kernel
queue.enqueueNDRangeKernel(kernel, cl::NullRange, cl::NDRange(n), cl::NullRange);
// Copy results back to host
queue.enqueueReadBuffer(c_buf, CL_TRUE, 0, n * sizeof(float), c.data());
// Print results
for (int i = 0; i < n; i++) {
std::cout << c[i] << " ";
}
std::cout << std::endl;
return 0;
}
这是相同操作的 SYCL 代码
#include <iostream>
#include <sycl/sycl.hpp>
void add_vectors(sycl::queue& queue, sycl::buffer<float>& a, sycl::buffer<float>& b, sycl::buffer<float>& c) {
sycl::range n(a.size());
queue.submit([&](sycl::handler& cgh) {
auto in_a = a.get_access<sycl::access::mode::read>(cgh);
auto in_b = b.get_access<sycl::access::mode::read>(cgh);
auto out_c = c.get_access<sycl::access::mode::write>(cgh);
cgh.parallel_for<class add_vectors>(n, [=](sycl::id<1> i) {
out_c[i] = in_a[i] + in_b[i];
});
});
}
int main(int, char**) {
const size_t n = 100;
std::vector<float> a(n, 1.0f);
std::vector<float> b(n, 2.0f);
std::vector<float> c(n, 0.0f);
sycl::buffer<float> a_buf{a};
sycl::buffer<float> b_buf{b};
sycl::buffer<float> c_buf{c};
sycl::queue q;
add_vectors(q, a_buf, b_buf, c_buf);
auto result = c_buf.get_access<sycl::access::mode::read>();
for (size_t i = 0; i < n; ++i) {
std::cout << result[i] << " ";
}
return 0;
}
让我们比较这两个实现,以确定 SYCL 相对于 OpenCL 的优势。
在 SYCL 中,内核函数使用常规函数声明定义,而在 OpenCL 中,内核使用 __kernel
关键字定义。__kernel
限定符声明了一个可以在 OpenCL 设备(或多个设备)上执行的函数。主机通常调用此函数,该函数只能在设备上执行。
在 SYCL 中,无需额外的关键字即可定义内核,代码也更简洁。内核可以在主机和设备上执行,并作为 命令组函数对象提交到 队列。SYCL 的队列监视针对特定设备的任务的完成情况。对于参数,SYCL 期望 sycl:buffer
对象。这些是 SYCL 对内存缓冲区的表示。在 OpenCL 中,我们使用指向全局内存中对象的指针,使用 __global
关键字和 float*
指针。OpenCL 使用显式内存管理,程序员负责在设备上显式分配和释放内存。SYCL 提供了一个内存模型,其中运行时系统处理内存的分配和释放。
在 SYCL 中,缓冲区对象存储在全局内存中,我们使用访问器(sycl::access
类)在内核中读取和写入全局内存中的数据。这些抽象提供了更高级别的接口来访问全局内存,而无需像 OpenCL 中那样进行额外的限定符。
要执行内核,我们使用 parallel_for
类,该类设置要处理的元素范围并指定要并行执行的内核函数。SYCL 的队列管理主机和设备之间的通信。在该内核中,我们创建一个 parallel_for
对象,该对象在 N 个元素的范围内(sycl::range n(N)
)执行 add_vectors
内核。在 OpenCL 中,我们使用 clEnqueueNDRangeKernel
函数指定内核的并行执行,该函数将全局和局部工作项的数量作为参数。OpenCL 提供了一个较低级的接口,而在 SYCL 中,parallel_for
类更抽象,使用起来更简单。
与 OpenCL 相比,SYCL 成熟并获得广泛采用的时间较短。但是,它在行业中一直受到关注,并被许多公司采用。例如,在 2018 年,Intel 发布了其第一版 oneAPI,其中 SYCL 是一个关键组成部分。SYCL 有许多不同的实现,例如 Intel® oneAPI DPC++ Library (oneDPL)、ComputeCpp、HipSYCL、NeoSYCL 和 triSYCL。
每个实现都有独特的功能。并且,随着 Codeplay 对 NVIDIA® 和 AMD GPU 硬件的 oneAPI 增加了支持,DPC++ 现在支持在 NVIDIA 和 AMD GPU 上进行原生编程,使 SYCL 能够提供与 CUDA 或 HIP 相媲美的性能结果,同时提供 CUDA、HIP 和 OpenCL 无法比拟的可移植性。
结论
OpenCL 和 SYCL 在支持各种加速器方面有相似的目标。虽然 SYCL 是应用程序级别跨架构编程的现代解决方案,但它并非旨在说服 OpenCL 用户迁移。OpenCL 在一段时间内仍将很重要,并将继续是 SYCL 编译器用于触及硬件的目标之一。
SYCL 有许多实现,它们提供附加功能并在硬件上启用异构编程。例如,SYCL 基于 LLVM 的编译器提供高级工具,通过容纳支持 NVIDIA 和 AMD 等供应商的插件,可以在多个供应商的 GPU 上提供出色的原生性能,并不断整合最新 SYCL 规范中的新功能。通过 Open SYCL(以前称为 hipSYCL),甚至还有一个 SYCL 实现,它构建在 AMD HIP 之上,并集成了最新 SYCL 规范的新功能。