oneDPL 赋能您的 C++ 应用程序,实现跨设备并行编程(SYCL)





0/5 (0投票)
本文简要介绍了 oneDPL,包括其组件 API 的概览,并通过一个初步的代码示例,展示了该库的实际应用。
异构计算的概念为当今的开发者提供了更强大的处理能力。通过将各种工作负载分配到多种不同的计算核心(如 CPU、GPU 和 FPGA),异构计算应用程序能够实现更高的性能和更高的能效。
SYCL* 编程模型允许您基于开放的行业标准,为 C++ 应用程序利用异构计算。它为您提供了针对新旧 C++ 代码所需架构组合的自由。在使用各种硬件时,您可以通过跨设备操作的并发执行来进一步提升应用程序的性能。
如果您旨在通过 C++ 和 SYCL 特性的精妙结合来实现数据并行,那么 Intel 提供的 oneAPI 标准框架内的 SYCL 实现(开源)已准备好为您提供协助!凭借标准 C++ 和 SYCL 的结合优势,此实现被称为 oneDPL,它解锁了 Intel 的高级直接编程语言,用于在加速架构上进行并行编程。它不仅包含了 lambda 函数和模板等标准 C++ 结构,还包含了 SYCL 规范。
本博客简要介绍了 oneDPL,包括其组件 API 的概览,并通过一个初步的代码示例,展示了该库的实际应用。
什么是 oneDPL?
oneDPL 是 Intel® oneAPI DPC++ Library 的缩写(DPC++ 是 SYCL 的 oneAPI 实现),它是 C++ STL 的一个扩展,用于并行和跨设备编程,使您能够创建具有在 CPU、GPU 和 FPGA 上加速 SYCL 内核的异构解决方案,从而在这些架构上实现最大的生产力和高性能。通过在 C++ STL API 之上添加并行化的 C++17 算法和加速的 SYCL 内核,它可以为使用核心 C++ 编程开发的相同应用程序带来性能提升。
oneDPL 允许您使用 Parallel STL (PSTL) 和 Boost.Compute* 并行计算库的自定义迭代器和算法扩展来实现跨架构编程,并且它与 Intel® DPC++ 兼容性工具集成,可以轻松地将 CUDA* 应用程序代码迁移到 SYCL 代码。
使用 oneDPL 组件实现的 SYCL 代码由 Intel® oneAPI DPC++/C++ 编译器进行编译,这是一款基于 Clang* 的 SYCL 兼容编译器,是充分利用 oneDPL 的卸载扩展所必需的。(注意,如果使用 GCC 等编译器,则会遇到某些限制和复杂情况。)
oneDPL 组件
oneDPL 主要使用 SYCL 实现 C++ 算法库。它通过两种类型的组件 API 实现并行编程和 SYCL 内核的使用:
- 并行 API
- SYCL 内核 API
让我们更深入地了解这些 API。
并行 API
此 API 提供了一组标准 C++ 和 SYCL 扩展的并行算法和执行策略。算法的顺序执行由标准 C++ 执行策略处理,而向量化和并行执行则遵循 SYCL 执行策略的实现。
详细了解 oneDPL 算法遵循的执行策略。
该实用程序 API 还提供了 特殊迭代器,如 counting_iterator、zip_iterator 和 permutation_iterator。作为 C++17 阻塞并行算法的扩展,oneDPL 提供了一些具有非阻塞行为的 异步算法。为了支持 C++20 Ranges 库,oneDPL 提供了范围工厂1和范围适配器2。此外,oneDPL 支持 基于范围的算法 和一些 杂项算法。
SYCL 内核 API
oneDPL 提供了一部分经过测试可与 SYCL 内核一起使用的标准 C++ API。其中一些是 std::swap
、std::lower_bound
、std::upper_bound
、std::log
、std::copy
和 std::transform
。完整的列表很长。
oneDPL 还允许使用 SYCL 内核进行 随机数生成 (RNG)。它提供了用于生成无符号整数序列随机数(伪随机数)的引擎。为了完成其 RNG 支持,它提供了随机数分布,根据统计概率密度函数分配随机数引擎的输出。
此外,oneDPL 还提供了定义实用函数对象的类,例如 identity、minimum 和 maximum。这些函数对象分别实现了 std::identity
、std::less
和 std::greater
等标准 C++ 函数。
oneDPL 代码示例:稳定排序
oneDPL 函数可用于实现各种算法,例如 伽马校正、凸包属性、点积和随机数生成。其中一些应用以 GitHub 代码示例 的形式提供给您。
现在您已经对 oneDPL 的组件 API 有了大致了解,让我们来看一个来自 oneDPL GitHub 仓库的综合代码示例,该示例展示了如何使用 oneDPL API 函数执行 按键稳定排序。它对两个序列进行排序——一个键序列和一个值序列,其中键被比较,但两个实体都会被交换。
该 按键稳定排序代码示例 使用了 <oneapi/dpl/iterator>
头文件中包含的特殊迭代器 counting_iterator
和 zip_iterator
。以及其他标准算法和执行策略所必需的头文件,如下所示:
#include <oneapi/dpl/algorithm>
#include <oneapi/dpl/execution>
#include <oneapi/dpl/iterator>
创建了两个单独的缓冲区——一个用于键,一个用于值,如下所示:
sycl::buffer<int> keys_buf{n}; //keys buffer
sycl::buffer<int> vals_buf{n}; //values buffer
其中“n”被定义为一个整数常量(此处为 1000000),足够大以容纳大量键值对。为简单起见,我们暂时考虑 n=10,以便您更好地理解排序输入和预期输出。
oneDPL 辅助函数 oneapi::dpl::begin
将键和值的 oneDPL 缓冲区传递给在后续排序过程中使用的 oneDPL 算法。
auto keys_begin = oneapi::dpl::begin(keys_buf);
auto vals_begin = oneapi::dpl::begin(vals_buf);
算法默认遵循 oneDPL 执行策略
auto policy = oneapi::dpl::execution::dpcpp_default;
使用 counting_iterator
和 std::copy
方法,值缓冲区被填充从 0 到 (n-1) 的数字。而键缓冲区则填充为 {n, n, n-2, n-2, n-4, n-4,…,6, 6, 4, 4, 2, 2}。
对于 n=10,缓冲区内容如下:
键 - {10, 10, 8, 8, 6, 6, 4, 4, 2, 2}
值 - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
按键稳定排序技术根据键缓冲区内容的升序对上述两个序列进行排序。对于具有相同内容的多个键,则遵循值的升序。因此,对于 n=10,预期的排序输出如下:
键: {2, 2, 4, 4, 6, 8, 8, 10, 10}
值: {8, 9, 6, 7, 4, 5, 2, 3, 0, 1}
一般来说,对于任何 n 值(例如代码示例中采用的 n=1000000),预期的输出如下:
键: {2, 2, 4, 4, n-2, n-2, n, n}
值: {n-2, n-1, n-4, n-3,…,2, 3, 0, 1}
使用 make_zip_iterator
函数,构造了一个 zip_iterator
,它包装了键和值缓冲区的迭代器。
auto zipped_begin = oneapi::dpl::make_zip_iterator(keys_begin, vals_begin);
然后,标准 C++ 函数 std::stable_sort
将按键稳定排序算法应用于 keys_begin
和 vals_begin
两个迭代器。它将这两个迭代器作为键值对包装在 zipped_begin
迭代器中进行处理。
std::stable_sort(policy, zipped_begin, zipped_begin+n, [](auto lhs, auto rhs) {return std::get<0>(lhs) < std::get<0>(rhs);});
这就是 oneDPL 如何用于按键稳定排序。请查看 源代码和 完整的代码示例 以获取更多信息。
您是否有兴趣尝试更多 oneDPL 的实际应用?请访问 GitHub 代码示例,深入了解这个惊人的库!
后续步骤
立即开始使用 oneDPL,熟悉其强大的功能,以整合标准 C++ 和 SYCL 的特性。查看下面的资源,并开始使用 oneDPL 开发跨架构 SYCL 应用程序!
有用资源
获取软件
您可以安装 oneDPL 的独立版本,或将其作为 oneAPI Base Toolkit 的一部分下载。您还可以在 Intel® Developer Cloud 上尝试跨 Intel® CPU 和 GPU 的 oneDPL 工作负载。
致谢
我们要感谢 Chandan Damannagari 对本博客的贡献。