CUDA计算能力2.0。全局内存访问模式

CUDA计算能力2.0。全局内存访问模式,cuda,Cuda,从CUDA Compute Capability 2.0(费米)开始,全局内存访问通过768 KB二级缓存工作。看起来,开发人员不再关心全局内存库了。但是全局内存仍然非常慢,因此正确的访问模式很重要。现在的重点是尽可能多地使用/重用L2。我的问题是,怎么做?如果我需要一些详细的信息,L2如何工作,以及如何组织和访问全局内存(例如,每个线程100-200个元素数组),我将不胜感激 二级缓存在某些方面有所帮助,但它并不排除合并访问全局内存的需要。简言之,合并访问意味着对于给定的读(或写)指令,扭曲中

从CUDA Compute Capability 2.0(费米)开始,全局内存访问通过768 KB二级缓存工作。看起来,开发人员不再关心全局内存库了。但是全局内存仍然非常慢,因此正确的访问模式很重要。现在的重点是尽可能多地使用/重用L2。我的问题是,怎么做?如果我需要一些详细的信息,L2如何工作,以及如何组织和访问全局内存(例如,每个线程100-200个元素数组),我将不胜感激

二级缓存在某些方面有所帮助,但它并不排除合并访问全局内存的需要。简言之,合并访问意味着对于给定的读(或写)指令,扭曲中的单个线程正在读取(或写入)全局内存中相邻的连续位置,最好在128字节边界上作为一个组对齐。这将使可用内存带宽得到最有效的利用

在实践中,这通常不难实现。例如:

int idx=threadIdx.x + (blockDim.x * blockIdx.x);
int mylocal = global_array[idx];
假设在全局内存中使用cudamaloc以普通方式分配
global_数组
,则将在一个warp中的所有线程之间提供联合(读取)访问。这种类型的访问使可用内存带宽得到100%的利用

关键的一点是,内存事务通常发生在128字节块中,这恰好是缓存线的大小。如果您请求一个块中的哪怕一个字节,整个块都将被读取(通常存储在L2中)。如果您稍后从该块读取其他数据,则通常会从L2为其提供服务,除非它已被其他内存活动逐出。这意味着以下顺序:

int mylocal1 = global_array[0];
int mylocal2 = global_array[1];
int mylocal3 = global_array[31];
通常都是从单个128字节块进行服务。
mylocal1
的第一次读取将触发128字节的读取。
mylocal2
的第二次读取通常是通过缓存值(在L2或L1中)进行的,而不是通过触发另一次从内存读取来进行。但是,如果可以适当地修改算法,那么最好从多个线程连续读取所有数据,如第一个示例中所示。这可能只是数据的巧妙组织问题,例如使用数组结构而不是结构数组

在许多方面,这类似于CPU缓存行为。缓存线的概念与为来自缓存的请求提供服务的行为类似

费米L1和L2可以支持回写和直写。L1在每个SM的基础上可用,并可配置为与共享内存拆分为16KB L1(和48KB SM)或48KB L1(和16KB SM)。L2在整个设备上是统一的,为768KB

我提供的一些建议是不要假设二级缓存只是修复了草率的内存访问。GPU缓存比CPU上的等效缓存小得多,因此更容易陷入麻烦。一条一般性的建议是简单地编写代码,就好像缓存不在那里一样。与面向CPU的策略(如缓存阻塞)不同,通常最好将编码工作集中在生成联合访问上,然后在某些特定情况下可能使用共享内存。对于无法在所有情况下进行完美内存访问的不可避免的情况,我们让缓存提供它们的好处


您可以通过查看一些可用的指南获得更深入的指导。例如,(and)或对本主题有指导意义。您可能还想阅读。

除了这个极好的答案之外,post Fermi硬件还有一个额外的专用只读L1(与正常的读写L1大小相同)。这意味着,如果有什么理由去关注这些“小细节”,那就是现在。@RobertCrovella回答得很好,但我有一个关于联合阅读的问题。对于全局内存的合并读取,我们必须使用
\uuuu syncthreads()。在上面的示例中,
intlocal1=global_数组[idx]
后面将跟有
\uuuu syncthreads()。问题是,如果我有多个数组,比如
intlocal1=global_array1[idx];int local2=global_array2[idx];int local3=global_array3[idx]
我将使用单个
\uuuuuSyncThreads()进行合并读取吗在所有这些定义之后?谢谢。合并读取和
\uu syncthreads()
没有任何关系。