cuda 共享内存bank conflict详解

在cuda并行计算中,共享内存在GPU速度优化上扮演着重要作用,但是如果共享内存使用不当,也会导致速度不快反降或者提速效果不佳,如发生bank conflict;

bank的中文翻译为存储体,GPU 共享内存是基于存储体切换的架构(bank-switched-architecture),一般现在的GPU都包含32个存储体,即共享内存被分成了32个bank;根据GPU计算能力的不同(Compute Capability),每个共享内存存储体的宽可以是32位(CC2.x)或64位(CC3.x以上),即连续的32-bits(或64-bits)字被分配到连续的32个bank中(计算能力不是描述GPU设备计算能力强弱的绝对指标,他是相对的,准确的说他是一个架构的版本号,他可以通过cudaDeviceSetSharedMemConfig() 配置成 cudaSharedMemBankSizeFourByte 四个字节或者 cudaSharedMemBankSizeEightByte(CC3.x以上) 。设置成8字节可以有效避免双精度数据的bank conflicts,默认是4字节), 但是这又遇到一个问题,以Telsa P100为例,我们切换bank的宽为32bit,即4个字节,那么32个bank仅仅为128B的内存,而Telsa P100的共享内存为48KB,那么多余的内存呢?

我们看到这32bit我们定义为宽,那么有宽就有高,在这个博客中https://segmentfault.com/a/1190000007533157,博主进行了这样的比喻:

在共享内存中,连续的32-bits字被分配到连续的32个bank中,这就像电影院的座位一样:一列的座位就相当于一个bank,所以每行有32个座位,在每个座位上可以“坐”一个32-bits的数据(或者多个小于32-bits的数据,如4个char型的数据,2个short型的数据);而正常情况下,我们是按照先坐完一行再坐下一行的顺序来坐座位的,在shared memory中地址映射的方式也是这样的

其中0-31为bank编号,如果申请一个共享内存数组__shared__ int cache[64],int 恰好为4个字节,那么cache[0]访问bank[0][0], cache[1]访问bank[0][1],...,cache[31]访问bank[0][31],cache超过32时,cache就会去访问下一行的bank,即cache[32]就会访问

bank[1][0],以此类推。

bank冲突就是在这样的条件下产生,即如果一个warp的多个线程访问同一个bank的不同字段时(注:不同字段如bank[0][0],bank[1][0],...,bank[n][0]),那么就发生了bank冲突,因为不同bank可以同时访问,而当如果多个线程请求的内存地址被映射到了同一个bank上,那么这些请求就变成了串行的。

在bank conflicts中,我们一直在强调同一warp,这是因为warp是GPU执行时的调度单位,即对于GPU的每个SM执行的一个block,事实上每一次仅有32个线程在同时执行,只是因为一个GPU有多组SM,每个SM可以同时处理多个block,所以同时处理的线程数也就多了。因此不同warp访问同一bank并不会造成冲突,因为事实上不同warp本来就不会同时访问bank。

下面来看一个bank conflict的例子:

__global__ void kernel1() //没有bank conflict
{
    int tid=threadIdx.x;
    __shared__ int cache[128];
    cache[tid*1]=1;
    int number=cache[tid*1];
}

__global__ void kernel2() //有bank conflict
{
    int tid=threadIdx.x;
    __shared__ int cache[128];
    cache[tid*4]=1;
    int number=cache[tid*4];
}

int main()
{
    kernel1<<<1,32>>>();
    kernel2<<<1,32>>>();
    return 0;
}

这个例子只用了1个block,保证32个线程为一个warp,分析kernel2,我们可知,线程0和线程8都会去访问bank[0],其中线程0访问bank[0][0],线程8访问bank[1][0](同理1,9...),这就发生了bankconflict; 理论上来说,kernel2的计算时间应该是比kernel1的4倍;

我们通过nvprof测量两个核的运行时间:

 

可以看到,kernel1和kernel2的执行时间差别并不大,甚至kernel1还略大于kernel2,这是为什么呢?

我查了很多资料,其中有一种说法,核函数启动也是需要时间的,一般是us级别, 但是对于连续的核函数启动,后面的Kernel启动延迟可以被隐藏掉(包括启动隐藏和执行隐藏)http://blog.sina.com.cn/s/blog_98740ded0102wjlc.html

我们看到本例启动的线程并不多,程序并不复杂,执行时间可能被隐藏时间抵消掉了,但是我们通过nvvp也可以看到效果(nvvp是nvprof的 图形版)

通过nvvp GPU Details分析,我们看到下图:

(注:nvprof,nvvp和cuda提供的计时函数cudaEventRecord计算出来的时间都不一样(有可能是硬件随机化,但是差别有点大,不太像,此处还不清楚)

从Shared Memory Efficiency处可以看到,kernel1的效率为100%,kernel2的效率为25%,刚好是我们前面分析的4倍。

前面我们定义bank conflict为一个warp多个线程访问同一个bank的不同字段,那么一个warp多个线程访问同一个bank的相同字段

呢?如同时访问bank[0][0]。结论是不会发生bank conflict,这就牵涉到GPU的广播和多播机制,详情可以查看博客https://segmentfault.com/a/1190000007533157

  • 25
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
### 回答1: CUDA共享内存是一种特殊的内存类型,它可以在同一个线程块内的线程之间共享数据。这种内存类型的访问速度非常快,因为它是在GPU芯片上的SRAM中实现的。使用共享内存可以有效地减少全局内存的访问,从而提高CUDA程序的性能。共享内存的大小是有限制的,通常为每个线程块的总共享内存大小的一半。因此,在使用共享内存时需要仔细考虑内存的使用情况,以避免内存溢出和性能下降。 ### 回答2: CUDA shared memory是一种专门用于加速GPU并行计算的高速缓存区域。它位于GPU的多个处理核心之间共享,并在同一个线程块中的线程之间交流数据。相比于全局内存,shared memory具有更低的访问延迟和更高的带宽。 shared memory可以通过声明__shared__关键字来定义,并通过静态分配的方式进行初始化。每个线程块都具有自己独立的shared memory空间,其大小在编译时确定,但最大限制为48KB。 shared memory的主要优点是其高带宽和低延迟。由于其位于多个处理核心之间共享,可以实现线程之间的快速数据交换。通过将计算中频繁使用的数据存储在shared memory中,可以减少从全局内存中读取数据所需的时间。这对于那些具有访存限制的算法,如矩阵乘法和图像处理等,非常有用。 使用shared memory还可以避免线程间的数据冗余读取,从而提高整体的并行计算效率。当多个线程需要访问相同的数据时,可以将这些数据存储在shared memory中,以便线程之间进行共享,从而减少了重复的全局内存访问。 但shared memory也有一些限制和需要注意的地方。首先,shared memory的大小是有限的,需要根据具体的算法和硬件限制进行适当调整。其次,由于其共享的特性,需要确保线程之间的数据同步。最后,使用shared memory时需要注意避免bank conflict,即多个线程同时访问同一个shared memory bank造成的资源竞争,从而导致性能下降。 综上所述,CUDA shared memory在GPU并行计算中具有重要的作用。通过使用shared memory,可以有效减少全局内存访问、提高数据交换速度和并行计算效率,从而加速GPU上的并行计算任务。 ### 回答3: CUDA共享内存(shared memory)是指在CUDA程序中使用的一种特殊的内存空间。它是GPU上的一块高速、低延迟的内存,被用来在同一个线程块(thread block)中的线程之间进行数据共享。 与全局内存相比,共享内存的访问速度更快,读写延迟更低。这是因为共享内存位于SM(Streaming Multiprocessor)内部,可以直接被SM访问,而全局内存则需要通过PCIe总线与主机内存进行通信。 使用共享内存可以提高应用程序性能的原因之一是避免了全局内存的频繁访问。当多个线程需要读写同一个数据时,如果每个线程都从全局内存中读取/写入,会导致内存带宽饱和,限制了整体性能。而将这些数据缓存在共享内存中,可以减少对全局内存的访问次数,提高内存带宽的利用率。 除此之外,共享内存的另一个重要特性是可以用作线程间的通信机制。在同一个线程块中的线程可以通过共享内存交换数据,而无需利用全局内存作为中介。这使得线程之间的协作变得更加高效和灵活。 然而,共享内存也有一些限制。首先,共享内存的大小是有限的,通常为每个SM的一定容量(如16KB或48KB)。其次,共享内存的生命周期与线程块相同,每个线程块结束后,共享内存中的数据将被销毁。 在编写CUDA程序时,可以使用__shared__关键字来声明共享内存。同时需要注意,合理地使用共享内存,并避免冲突和竞争条件,才能充分发挥共享内存的优势,提高CUDA程序的性能。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值