OneAPI异构并行编程实践

实验目的:

搭建本地的DPCPP编译器环境/按照步骤指南申请Intel DevCloud账号;
初步了解Data Parallel C++编写异构并行程序;
完成相关练习;

 实验环境搭建:

1. 基于Intel DevCloud环境
2. Dpcpp编译器的本地安装
3. 基于Docker的环境

 1.基于Intel DevCloud环境

在浏览器中访问下列快捷网址, https://idzcn.com/devcloud.htm ,或直接访问账户注册页面
https://www.intel.com/content/www/cn/zh/secure/forms/devcloud/enrollment.html 开始注册一个新的英特尔用户账号。

 第一步:点击“Create an Account”开始创建一个全新的英特尔账号。

第二步:填写基本信息。

第三步: 一直点击“创建”或“下一步”向下进行注册工作。

 第四步:注册激活完成后,点击Overview | Intel® DevCloud进入DevCloud for oneAPI 专项服务页面中,点击中间或右上角的“Sign In”链接,输入完成登录。

第五步:勾选“I accept these terms”,并登录进入专项服务。此时,会在网页右上角显示账户信息及有效期。

第六步:通过点击网页左侧“Get Started”进入该选项的页面,在“Get Started”选项页面中,通过点击页面最左下角 Connect with Jupyter* Lab 中的“Launch JupyterLab*”按钮直接启动Jupypter 服 务。

第七步: 启动 Jupyter 服务的过程中,系统可能会再次提示确认登入信息,并开始部署并启动相关远程服务。其中可能会有若干次系统显示的部署进展提示信息。

第八步:Jupypter 服务启动完成后,选择将缺省的 Python3 作为当前运行的核心。

第九步:可以点击右上角+号,创建一个文件夹供学习使用。

第十步: 通过创建Python3的Notebook文件,来进行相关实验的编写,同时利用Other中Terminal进行终端编译运行工作。

以上就是 基于Intel DevCloud环境搭建工作。

 2. Dpcpp编译器的本地安装

第一步:进入https://github.com/intel/llvm/releases网址,选择最新版本的进行下载。

第二步:在特定目录对压缩包进行解压 (例如,C:/opt)

第三步:把llvm编译器的bin目录添加到系统环境变量PATH (如C:/opt/dpcpp_compiler/bin),注意PATH一定是要大写的,不然可能还是会有问题。

第四步:在本地创建一个cpp文件,然后进入文件所在位置,利用终端进行编译执行。

clang++ -fsycl test.cpp -o test.exe

./test.exe

3. 基于Docker的环境

 按照以下网址按步骤做即可。https://github.com/intel/llvm/blob/sycl/sycl/doc/developer/DockerBKMs.md#sycl-containers-overview

 实验过程及结果分析:(以下实验都是基于Intel DevCloud环境

1.basic_parafor: 一个基本的for循环并行化示例

        新建一个hello.cpp文档,将以下代码拷贝粘贴写入,保存退出后。在新建终端中进行编译,编译命令为icpx -fsycl hello.cpp -o hello.exe,然后利用./ hello.exe命令运行该exe文档,得到所需结果。相关代码及结果图如下:

#include <CL/sycl.hpp>
#include <iostream>
using namespace sycl;
const std::string secret {
"Ifmmp-!xpsme\"\012J(n!tpssz-!Ebwf/!"
"J(n!bgsbje!J!dbo(u!ep!uibu/!.!IBM\01"};
const auto sz = secret.size();
int main() {
    queue Q;
    char *result = malloc_shared<char>(sz, Q);
    std::memcpy(result, secret.data(), sz);
    Q.parallel_for(sz, [=] (auto &i) {
    result[i] -= 1;
    }).wait();
    std::cout << result <<"\n";
    return 0;
}

 代码结果分析:

      这段代码是一个使用SYCL进行并行编程的示例,目的是对字符串进行加密。定义了字符串常量secret,并使用std::string类型进行初始化。同时,定义了一个整型常量sz,保存secret字符串的长度。然后使用构造函数创建SYCL队列Q,该队列可以直接在设备或主机上执行命令。并使用malloc_shared函数为共享的内存分配sz个字节的空间,并返回指向该内存区域的指针result。使用SYCL的parallel_for函数,在队列Q上并行执行for循环,将共享内存中的数据进行加密。该函数的第一个参数sz表示循环的次数,第二个参数是一个lambda函数,表示并行执行的代码。该lambda函数中的变量&i是循环计数器,由SYCL自动分配和管理。接着等待加密操作完成,最后使用std::cout输出加密后的结果,再释放掉掉共享内存。而输出结果如上图所示,是加密后的字符串内容。

2.DPC++设备选择方式1:默认的设备选择器

       新建一个test1.cpp文档,将以下代码写入,保存退出后。在新建终端中进行编译,编译命令为icpx -fsycl test1.cpp -o test1.exe,然后利用./ test1.exe命令运行该exe文档,得到所需结果。相关代码及结果图如下:

#include <CL/sycl.hpp>
#include <iostream>
using namespace sycl;
int main(){
    queue Q;
    std::cout << "Select device: "<<
    Q.get_device().get_info<info::device::name>() << "\n";
    return 0;
}

代码结果分析:

        这段代码使用SYCL查询和输出默认设备信息。首先,使用缺省构造函数创建SYCL队列Q,该队列可以直接在设备或主机上执行命令。然后,使用SYCL的get_device和get_info函数查询设备的名称,并使用std::cout输出。其中,get_device返回该队列使用的设备对象,get_info<>()函数可以查询设备的不同属性值,这里使用了info::device::name()来获取设备的名称属性。最后,使用std::cout输出设备名称,以及一些提示信息。而其输出结果如上图所示,是当前SYCL队列所使用的设备的名称。

 3.DPC++设备选择方式2:使用host_selector 

       新建一个test2.cpp文档,将以下代码写入,保存退出后。在新建终端中进行编译,编译命令为icpx -fsycl test2.cpp -o test2.exe,然后利用./ test2.exe命令运行该exe文档。由于新版本的sycl已经不再支持host_selector操作,在DevCloud中无法执行,代码如下:

#include <CL/sycl.hpp>
#include <iostream>
using namespace sycl;
int main(){
    queue Q (host_selector{} );
    std::cout << "Select device: "<<
    Q.get_device().get_info<info::device::name>() << "\n";
    std::cout <<" -> Device vendor: " <<
    Q.get_device().get_info<info::device::vendor>() << "\n";
    return 0;
}

代码结果分析:

     这段代码也是使用SYCL查询和输出主机设备的信息。首先,使用host_selector{}构造函数创建SYCL主机队列Q,该队列将在主机上执行SYCL命令。然后,使用SYCL的get_device和get_info函数查询设备的名称和厂商信息,并使用std::cout输出。get_device返回该队列使用的设备对象,get_info<>()函数可以查询设备的不同属性值,这里分别使用了info::device::name()和info::device::vendor()来获取设备的名称和厂商信息。最后使用std::cout输出设备名称和厂商信息。其输出结果应该为设备的名称和厂商信息,但由于新版本的sycl已经不再支持host_selector操作,在DevCloud中无法执行,无法显示结果。

 4.DPC++设备选择方式3:使用cpu_selector

        新建一个test3.cpp文档,将以下代码写入,保存退出后。在新建终端中进行编译,编译命令为icpx -fsycl test3.cpp -o test3.exe,然后利用./ test3.exe命令运行该exe文档,得到所需结果。相关代码及结果图如下:

#include <CL/sycl.hpp>
#include <iostream>
using namespace sycl;
int main(){
    queue Q ( cpu_selector_v );
    std::cout << "Select device: "<<
    Q.get_device().get_info<info::device::name>() << "\n";
    std::cout <<" -> Device vendor: " <<
    Q.get_device().get_info<info::device::vendor>() << "\n";
    return 0;
}

代码结果分析:

        这段代码还是使用SYCL查询和输出CPU设备的信息。首先,使用cpu_selector_v构造函数创建SYCL CPU队列Q,该队列将在CPU上执行SYCL命令。然后,使用SYCL的get_device和get_info函数查询设备的名称和厂商信息,并使用std::cout输出。get_device返回该队列使用的设备对象,get_info<>()函数可以查询设备的不同属性值,这里分别使用了info::device::name()和info::device::vendor()来获取设备的名称和厂商信息。最后使用std::cout输出设备名称和厂商信息。其输出结果如上图所示,是当前SYCL队列所使用的CPU设备的名称及厂商信息。

 5.DPC++设备选择方式5:使用多个selector

        新建一个test4.cpp文档,将以下代码写入,保存退出后。在新建终端中进行编译,编译命令为icpx -fsycl test4.cpp -o test4.exe,然后利用./ test4.exe命令运行该exe文档,得到所需结果。相关代码及结果图如下:

#include <CL/sycl.hpp>
#include <iostream>
using namespace sycl;
int main(){
        queue my_default_queue(default_selector_v);
        queue my_accelerator_queue(accelerator_selector_v);
        std::cout << "Selected device 1:" << my_default_queue.get_device().get_info<info::device::name>() << "\n";
        std::cout << "Selected device 2:" << my_accelerator_queue.get_device().get_info<info::device::name>() << "\n";
        return 0;
}

代码结果分析:

        这段代码使用SYCL创建了两个队列,并输出它们所选用的设备的名称。首先,使用default_selector_v和accelerator_selector_v构造函数分别创建了两个SYCL队列对象my_default_queue和my_accelerator_queue。其中,default_selector_v会选择一个系统可用设备作为默认设备来执行计算任务,而accelerator_selector_v会选择一个可以加速计算的设备。这样就可以为不同的计算任务选择不同的设备来获得最优的性能和效率。然后,使用get_device和get_info函数查询每个队列所选用设备的名称,并使用std::cout输出。这里分别使用了info::device::name()来获取设备名称属性。最后使用std::cout输出设备名称和一些提示信息。其输出结果如上图所示,是会有两行,分别是两个队列所选用的设备名称。

 6.向数组中填充数据

       新建一个test5.cpp文档,将以下代码写入,保存退出后。在新建终端中进行编译,编译命令为icpx -fsycl test5.cpp -o test5.exe,然后利用./ test5.exe命令运行该exe文档,得到所需结果。相关代码及结果图如下:

#include <CL/sycl.hpp>
#include <array>
#include <iostream>
using namespace sycl;
int main(){
        constexpr int size = 16;
        std::array<int,size> data;
        buffer  B {data};
        queue Q{};
        std::cout << "Selected device is : " << Q.get_device().get_info<info::device::name>() << "\n";
        Q.submit([&](handler& h){
                                accessor acc{B,h};
                                h.parallel_for(size,[=](auto&idx){
                                                        acc[idx] = idx;
                                                });
                        });
        return 0;
}

代码结果分析:

        这段代码使用SYCL并行计算初始化了一个包含16个整数的数组,并输出了使用的设备名称。首先,设置了一个包含16个整数的数组data,使用std::array<int,size>来定义。然后,创建了一个SYCL缓冲区对象B,使用data初始化,该缓冲区对象用于在程序执行过程中存储和管理一组数据。接着,创建一个默认的SYCL队列对象Q,并使用Q.get_device()获取设备信息。运行Q.submit函数,并传递了一个lambda函数。这个lambda函数的参数中包含一个处理程序handler和一个数据访问器accessor。由于我们在Q.submit中使用了[=]来记录一下handler对象,因此这个lambda函数也可以访问外部作用域的数据。其中,accessor对象是用来访问缓冲区数据的一个辅助对象,h.parallel_for函数实现了一个并行for循环,执行索引数组中的内容。这里的并行计算就是将数组的每个位置都初始化为该位置的下标。这个lambda函数将使用数组元素的下标值来初始化数组元素。其输出结果如上图所示,是当前SYCL队列所使用的设备的名称。

实验总结

         在本次实验中,我掌握了SYCL编程模型的基本概念和使用方法,这将对我在并行计算开发中带来了更深入的理解。SYCL编程模型基于OpenCL实现,提供了一种高效且易于使用的并行编程方式。同时,我学习到了 SYCL 编程模型中与数据共享相关的核心概念,包括缓冲和访问器对象的使用,以及如何使用设备选择器和队列对象来调度多个硬件设备执行并行计算任务。这些内容对于理解 SYCL 编程模型的实际应用具有重要意义。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值