C++ OpenCL内存带宽/合并 总结:

C++ OpenCL内存带宽/合并 总结:,c++,gpu,opencl,gpgpu,memory-bandwidth,C++,Gpu,Opencl,Gpgpu,Memory Bandwidth,我正试图编写一个内存绑定的OpenCL程序,它接近我的GPU上公布的内存带宽。事实上,我的差距是50倍 设置: 我只有一张相对较旧的Polaris卡(RX580),所以我不能使用CUDA,现在只能选择OpenCL。我知道这是次级的,我不能让任何调试/性能计数器工作,但这就是我的全部 我是GPU计算的新手,我想体验一下我所期望的一些性能 从GPU到CPU。我首先要做的是内存带宽 我编写了一个非常小的OpenCL内核,它从跨越的内存位置进行读取,我希望波前中的所有工作人员一起在一个大内存段上执行连续

我正试图编写一个内存绑定的OpenCL程序,它接近我的GPU上公布的内存带宽。事实上,我的差距是50倍

设置: 我只有一张相对较旧的Polaris卡(RX580),所以我不能使用CUDA,现在只能选择OpenCL。我知道这是次级的,我不能让任何调试/性能计数器工作,但这就是我的全部

我是GPU计算的新手,我想体验一下我所期望的一些性能 从GPU到CPU。我首先要做的是内存带宽

我编写了一个非常小的OpenCL内核,它从跨越的内存位置进行读取,我希望波前中的所有工作人员一起在一个大内存段上执行连续的内存访问,合并访问。然后,内核对加载的数据所做的一切就是将这些值相加,并在最后将总和写回另一个内存位置。代码(大部分是我无耻地从各种来源一起复制的)非常简单

__kernel void ThroughputTestKernel(
                     __global float* vInMemory,
                     __global float* vOutMemory,
                     const int iNrOfIterations,
                     const int iNrOfWorkers
                   )
{
    const int gtid = get_global_id(0);
    
    __private float fAccumulator = 0.0;
    
    for (int k = 0; k < iNrOfIterations; k++) {
        fAccumulator += vInMemory[gtid + k * iNrOfWorkers];
    }
    
    vOutMemory[gtid] = fAccumulator;
}
\u内核通过按钮内核无效(
__全局浮点*内存,
__全球浮动*vOutMemory,
常数int迭代,
工作人员内部常数
)
{
const int gtid=get_global_id(0);
__专用浮点累积器=0.0;
for(int k=0;k
我生成这些内核的
iNrOfWorkers
,并测量它们完成处理所需的时间。对于我的测试,我设置了
iNrOfWorkers=1024
inrofitrations=64*1024
。根据处理时间和
iMemorySize=iNrOfWorkers*iNrOfIterations*sizeof(float)
I计算出大约5GByte/s的内存带宽

期望: 我的问题是,内存访问似乎比我认为可用的256GByte/s慢一到两个数量级

GCN ISA手册[1]假设我有36个CU,每个CU包含4个SIMD单元,每个单元的处理向量包含16个元素。因此,我应该有36416=2304个处理元素可用

我生成的数量少于该数量,即1024个全局工作单位(“线程”)。线程按顺序访问内存位置,相隔1024个位置,因此在循环的每次迭代中,整个波前访问1024个连续元素。因此,我相信GPU应该能够产生连续的内存地址访问,其间没有中断

我的猜测是,不是1024个,它只产生很少的线程,每个CU可能一个?那样的话,它就必须一次又一次地重新读取数据。不过,我不知道如何才能证实这一点


[1] 您的方法有几个问题:

  • 你不会使GPU饱和。要获得最高性能,您需要启动比GPU的执行单元多得多的线程。更多意味着超过10000000
  • 您的循环包含索引整数计算(用于联合访问的结构数组)。在这里,这可能不足以让您进入计算极限,但通常最好使用
    #pragma unroll
    展开小循环;然后编译器已经完成了所有的索引计算。C++还可以将常数<代码> IrfFrutsOuts< /C>和<代码> IrrWorksUs/Cuff>右到OpenCL代码中,并用<代码>定义定义16次< /代码> /<代码>,通过C++字符串连接或硬编码定义InWorkWorks[4]/代码> ./LI>
根据您的访问模式,有4种不同的内存带宽:合并/未对齐读/写。合并比未对齐快得多,并且未对齐读取的性能损失小于未对齐写入。只有合并内存访问才能使您接近公布的带宽。您可以测量
inrofitrations
合并读取和1个合并写入。要分别测量所有四种类型,可以使用以下方法:

定义定义15728640 #定义定义M 16 内核无效基准_1(全局浮点*数据){ consuint n=获取全局id(0); #布拉格展开
对于(uint i=0;i您的方法的一些问题:

  • 您不会使GPU饱和。要获得最高性能,您需要启动比GPU的执行单元多得多的线程。更多意味着>10000000
  • 您的循环包含索引整数计算(用于联合访问的结构数组)。在这里,这可能不足以让您进入计算极限,但通常最好使用
    #pragma unroll
    展开小循环;然后编译器已经完成所有索引计算。您还可以使用
    #define将常量
    inrofitrations
    iNrOfWorkers
    直接烘焙到OpenCL代码中In rFrut迭代16 < /代码> />代码>通过C++字符串连接或硬编码定义15728640个工作人员< />代码。
根据您的访问模式,有4种不同的内存带宽:合并/未对齐的读取/写入。合并比未对齐快得多,且未对齐读取的性能损失小于未对齐的写入。只有合并内存访问才能使您接近公布的带宽。您可以测量
iNrOfIterations
coalesced读取和1合并写入。要分别测量所有四种类型,可以使用以下方法:

定义定义15728640 #定义定义M 16 内核无效基准_1(全局浮点*数据){ consuint n=获取全局id(0); #布拉格展开
对于(uint i=0;我非常感谢您提供这段代码!我运行了所有这些代码,结果都在4.5到5.5 GByte/s之间!合并的代码在上端,未对齐的代码在下端,但它们仍然远离我的预期!我确实对您的ba有一些疑问