为何仅转置CUDA网格而非线程块仍会导致计算性能下降?
这个问题其实戳中了CUDA内存模型里很容易被忽略的细节——网格维度的转置会直接破坏内存访问的局部性与合并特性,哪怕线程块内部的结构完全不变。结合你提到的L2缓存带宽受影响的现象,我可以从几个核心角度拆解原因:
内存访问合并彻底失效
咱们先回忆下CUDA内存访问的基本逻辑:线程块内的线程是按 warp(32个线程)为单位调度的,连续的线程访问连续的内存地址时,硬件会把这些请求合并成一个或几个大的全局内存请求,最大化带宽利用率。
原本你的block(x,y)处理d(x,y)区域时,线程块内的线程(比如tx∈[0,BlockX), ty∈[0,BlockY))访问的是d[y*BlockY + ty][x*BlockX + tx](假设数据是行优先存储),这时候同一warp内的线程访问的是连续的内存地址,合并效率拉满。但转置网格后,block(y,x)处理d(x,y),线程访问的变成了d[x*BlockX + tx][y*BlockY + ty]——这相当于按列访问内存,同一warp内的线程地址间隔是整个数据行的大小(比如每行有N个元素,地址间隔就是N*sizeof(T)),完全无法合并成大请求。零散的小请求会让L2缓存疲于奔命,带宽利用率直接暴跌。L2缓存的空间局部性被彻底浪费
L2缓存的核心价值是复用相邻线程块访问的数据。原本x方向相邻的线程块,访问的是连续的内存区域,这些数据加载到L2后,下一个线程块可以直接复用。但转置网格后,x方向相邻的线程块变成了原来y方向的块,它们访问的是不同行的同一列区域——这些地址在全局内存里是分散的,L2缓存里的缓存行根本没法被后续线程块复用。结果就是每一个线程块都要从全局内存重新加载数据,L2缓存命中率骤降,带宽压力直接拉满。缓存行利用率低到离谱
不管是L1还是L2,缓存行都是按连续内存地址加载的(比如256字节的缓存行对应64个float元素)。行访问时,一个缓存行能被整整一个warp的线程复用;但列访问时,每个线程只会用到缓存行里的一个元素,剩下的63个元素完全浪费。这意味着L2缓存需要加载更多的缓存行才能满足计算需求,直接导致L2带宽被无效占用,性能自然下降。SM的请求聚合效率大打折扣
CUDA的SM会对多个线程的内存请求做聚合,减少全局内存的访问次数。但当访问模式是列方向时,线程的地址跨度太大,SM无法将这些请求有效聚合,只能发送大量独立的小请求。L2缓存处理这些小请求的开销远大于处理大请求,进一步加剧了带宽的浪费。
总结下来,哪怕你只转置了网格维度,没有动线程块,但本质上是把连续的行访问模式改成了零散的列访问模式——这直接命中了CUDA内存模型的痛点,从访问合并、缓存局部性到缓存行利用率全面拉胯,最终表现为L2缓存带宽异常和性能下降。
内容的提问来源于stack exchange,提问作者Cristobal Navarro

