在SYCL中,内存分为三种类型:主机内存、设备内存和统一共享内存(USM)。对于主机和设备内存,数据交换需要显式复制。同时,进出 USM 的数据移动由 SYCL 运行时自动隐式管理。
sycl::malloc_device()
不幸的是,在使用 SYCL 为数值内核实现 GPU 加速的过程中,我发现仅通过切换到到,性能就会下降高达 4000% sycl::malloc_shared()
- 即使我所做的只是重复重新提交相同的 SYCL 内核,而不做任何尝试从主机访问数据。
当使用针对 AMD HIP GFX906 (Radeon VII / Instinct MI50) 的 OpenSYCL 构建代码时sycl::malloc_device()
,程序在 0.27 秒内完成:
$ time ./fdtd.elf
simulate 16974593 cells for 10 timesteps.
real 0m0.271s
user 0m0.253s
sys 0m0.020s
Run Code Online (Sandbox Code Playgroud)
当使用 构建相同的代码时sycl::malloc_shared()
,程序需要 10.6 秒才能完成:
simulate 16974593 cells for 10 timesteps.
real 0m10.649s
user 0m15.172s
sys 0m0.196s
Run Code Online (Sandbox Code Playgroud)
这是 3925% 的减速。
在BIOS中启用“4G以上解码”和“可调整BAR大小”支持后,现在需要3.8秒而不是10.6秒。但这并不能解决不必要的内存传输的实际问题 - 1300% 的性能损失仍然相当显着。
我之前也使用英特尔 DPC++ 编译器测试了类似的内核,并在相同的硬件上看到了类似的结果。
我怀疑速度减慢是由不必要的主机和设备复制引起的,但我不确定。SYCL 运行时使用什么启发法来确定是否需要复制?
下面附有示例代码。
ArrayNXYZ.hpp:4维数组(n,x,y,z)包装类。 …
我正在开发与 oneAPI 基础套件一起提供的 vectorAdd 示例应用程序。当我尝试在内核中打印总和时,出现以下错误。
请查看附件中的源代码以及编译时的错误。
//Source code
cgh.parallel_for<class VectorAdd>(num_items, [=](id<1> wiID) {
sum_accessor[wiID] = addend_1_accessor[wiID] + addend_2_accessor[wiID];
std::cout<<"Sum : "<<sum_accessor[wiID]<<std::endl; // I want to print this sum
});
Run Code Online (Sandbox Code Playgroud)
在编译期间我收到以下错误。
//Error generated while compiling
usr/lib/gcc/x86_64-linux-gnu/7.4.0/../../../../include/c++/7.4.0/bits/ostream.tcc:359:25: error: SYCL kernel cannot have a class with a virtual function table
extern template class basic_ostream<char>;
^
vector-add.cpp:159:6: note: used here
std::cout<<"Sum : "<<sum_accessor[wiID]<<std::endl;
^
vector-add.cpp:159:11: error: SYCL kernel cannot use a global variable
std::cout<<"Sum : "<<sum_accessor[wiID]<<std::endl;
Run Code Online (Sandbox Code Playgroud) 假设我有一组数据,例如大小为 N 的 3D 向量数组。假设我的 SYCL 内核的每次迭代专门或主要只涉及一个向量。一般来说,以下哪种将其分解为连续缓冲区的方法更有效——或者重要吗?
我意识到目标设备对此影响很大,所以我们假设它是一个独立的 GPU(即数据确实必须复制到不同的内存芯片,并且该设备没有像 FPGA 这样的疯狂架构 - 我是主要针对通过 CUDA 的 GTX 1080,但我预计当代码编译为 OpenCL 或我们使用另一个现代 GPU 时答案可能类似。
sycl::buffer<float> x, y, z;
,每个大小为 N。这样,在访问它们时,我可以使用sycl::id<1>
传递给内核的 lambda 作为索引,无需进行数学运算。(我怀疑编译器可能能够对此进行优化。)sycl::buffer<float> coords;
大小为 3N。sycl::id<1>
当使用调用访问它们时i
,我会获取 x 坐标buffer_accessor[3*i]
、y 坐标buffer_accessor[3*i+1]
和 z 坐标buffer_accessor[3*i+2]
。(我不知道编译器是否可以对此进行优化,并且我不确定对齐问题是否会发挥作用。)struct Coord { float x,y,z; }; sycl::buffer<Coord> coords;
. 由于对齐填充,这会增加内存使用量,在本例中增加 33%,这会带来相当惊人的成本,这也会增加将缓冲区复制到设备所需的时间。但代价是您可以在不操作 的情况下访问数据sycl::id<1>
,运行时只需处理一个缓冲区,并且设备上不应该出现任何缓存行对齐效率低下的情况。我找不到任何关于数据架构的指南来获得对此类事情的直觉。现在(4)看起来很愚蠢,(3)涉及不可接受的内存浪费,我正在使用(2),但想知道我是否不应该使用(1)来避免 id 操作和 3*sizeof(float)对齐的访问块。
使用英特尔 OneAPI beta6 的以下玩具代码。
#include <CL/sycl.hpp>
#include <iostream>
namespace sycl = cl::sycl;
const int SIZE=1;
class Increment_accessor {
public:
Increment_accessor(sycl::accessor<int, 1, sycl::access::mode::read_write, sycl::access::target::global_buffer> ptr_) : ptr {ptr_} {}
void operator()(sycl::item<1> item) {
ptr[item.get_linear_id()]++;
}
private:
sycl::accessor<int, 1, sycl::access::mode::read_write, sycl::access::target::global_buffer> ptr;
};
class Increment_pointer {
public:
Increment_pointer(sycl::global_ptr<int> ptr_) : ptr {ptr_} {}
void operator()(sycl::item<1> item) {
ptr[item.get_linear_id()]++;
}
private:
sycl::global_ptr<int> ptr;
};
int
main(int argc, char *argv[])
{
sycl::device dev = sycl::default_selector().select_device();
sycl::queue q(dev);
int hbuffer[SIZE] = {};
{
sycl::buffer<int, 1> …
Run Code Online (Sandbox Code Playgroud) 我是 DPC++ 的新手,我尝试开发一个基于 MPI 的 DPC++ 泊松解算器。我读了这本书,对缓冲区和指针与共享或主机内存感到非常困惑。这两件事有什么区别,我开发代码时应该使用什么。
现在,我使用由具有 const 大小的 std::array 初始化的缓冲区作为串行代码,并且效果良好。然而,当我将 DPC++ 代码与 MPI 耦合时,我必须为每个设备声明本地长度,但我没有这样做。这里我附上我的代码
define nx 359
define ny 359
constexpr int local_len[2];
global_len[0] = nx + 1;
global_len[1] = ny + 1;
for (int i = 1; i < process; i++)
{
if (process % i == 0)
{
px = i;
py = process / i;
config_e = 1. / (2. * (global_len[1] * (px - 1) / py + global_len[0] * (py - 1) / …
Run Code Online (Sandbox Code Playgroud)