我将改进OCL内核性能,并希望澄清内存事务的工作方式以及内存访问模式真正更好(以及为什么)。 内核由8个整数的向量提供,这些向量定义为数组: int v [8] ,这意味着,在进行任何计算之前,必须将整个向量加载到GPR中。所以,我认为这段代码的瓶颈是初始数据加载。
首先,我考虑一些理论基础。
目标硬件是Radeon RX 480/580,它具有256位GDDR5存储器总线,其上突发读/写事务具有8个字的粒度,因此,一个存储器事务读取2048位或256字节。我相信,CL_DEVICE_MEM_BASE_ADDR_ALIGN指的是:
Alignment (bits) of base address: 2048.
因此,我的第一个问题是:128字节高速缓存行的物理意义是什么?它是否保留了单个突发读取但未真正请求的数据部分?如果我们请求32或64字节,其余部分会发生什么 - 因此,剩余的超出缓存行大小? (我想,它会被丢弃 - 然后,哪一部分:头部,尾部......?)
现在回到我的内核,我认为缓存在我的情况下不起重要作用,因为一个突发读取64个整数 - >一个内存事务理论上可以同时提供8个工作项,没有额外的数据可读,内存总是合并。
但是,我仍然可以使用两种不同的访问模式来放置数据:
1)连续
a[i] = v[get_global_id(0) * get_global_size(0) + i];
(实际上已经执行过)
*(int8*)a = *(int8*)v;
2)交错
a[i] = v[get_global_id(0) + i * get_global_size(0)];
我希望在我的情况下连续会更快,因为如上所述,一个内存事务可以完全填充8个带有数据的工作项。但是,我不知道,计算单元中的调度程序是如何物理工作的:它是否需要为所有SIMD通道准备好所有数据,或者只需要4个并行SIMD元素的第一部分就足够了?尽管如此,我认为只要CU可以独立执行命令流,它就足够聪明,可以首先完全提供至少一个CU的数据。 在第二种情况下,我们需要执行8 * global_size / 64事务以获得完整的向量。
所以,我的第二个问题:我的假设是对的吗?
现在,这种做法。
实际上,我将整个任务分成两个内核,因为一个部分的注册压力比另一个部分少,因此可以使用更多的工作项。所以首先我使用模式如何在内核之间转换存储数据(使用vload8 / vstore8或者转换为int8给出相同的结果),结果有些奇怪:以连续方式读取数据的内核工作速度提高了大约10%(两者都在CodeXL和OS时间测量),但连续存储数据的内核执行速度惊人地慢。那么两个内核的总时间大致相同。在我的想法中,两者必须至少以相同的方式运行 - 要么更慢或更快,但这些反向结果似乎无法解释。
我的第三个问题是:有人可以解释这样的结果吗?或者我可能做错了什么? (或者完全错了?)
答案 0 :(得分:0)
查看AMD OpenCL Optimization Guide中的第2.1章。它主要关注老一代卡,但GCN架构并未完全改变,因此仍应适用于您的设备(polaris)。
通常,AMD卡具有多个存储器控制器,在每个时钟周期内分配存储器请求。例如,如果您以column-major而不是row-major逻辑访问您的值,则性能会更差,因为请求被发送到同一个内存控制器。 (按列专业,我的意思是矩阵的一列是由当前时钟周期中执行的所有工作项一起访问的,这就是你所说的合并与交错)。如果在单个时钟周期中访问一行元素(意味着合并)(意味着所有工作项访问同一行中的值),那些请求应该分配给不同的内存控制器而不是相同。
关于对齐和缓存行大小,我想知道这是否真的有助于提高性能。如果我在你的情况下,我会试着看看我是否可以优化算法本身,或者我是否经常访问这些值,将它们复制到本地内存是有意义的。但是,在没有任何关于内核执行的知识的情况下,很难再说出来。
最诚挚的问候,
迈克尔
答案 1 :(得分:0)
好吧,并没有真正回答所有我的问题,但是在互联网的广泛中找到的一些信息把事情放在一起更加清晰,至少对我来说(不像上面提到的AMD优化指南,这似乎不清楚,有时令人困惑) ):
«硬件执行一些合并,但它很复杂......
warp中的内存访问不一定必须是连续的,但它们落入多少32字节全局内存段(和128字节l1缓存段)并不重要。内存控制器可以在单个事务中加载这些32字节段中的1,2或4个,但是通过128字节高速缓存行中的高速缓存读取。
因此,如果warp中的每个通道都加载128字节范围内的随机字,那么就没有惩罚;这是一次交易,阅读效率很高。但是,如果warp中的每个通道加载4个字节,步长为128个字节,那么这非常糟糕:加载了4096个字节,但只使用了128个字节,效率提高了约3%。»
因此,对于我的情况,它并不重要,因为数据在读取/存储时始终是连续的,但载入部分的顺序可能会影响编译器的后续命令流(重新)调度。 /> 我也可以想象较新的GCN架构可以执行缓存/合并写入,这就是为什么我的结果与优化指南提示的结果不同。