CUDA grid stride循环下线程块数配置优化问题
先纠正你代码里的一个显性bug:你计算线程块数量的写法是错的。
// 现有错误写法:N_array和n_threads都是int类型,N_array / n_threads会先做整数除法截断小数,再转成float传给ceil,等于ceil完全**没起作用** int n_blocks = ceil(float(N_array / n_threads)); // 正确的整数向上取整写法,无浮点精度问题,是CUDA编程里的通用写法 int n_blocks = (N_array + n_threads - 1) / n_threads; // 如果要用ceil,必须把其中一个运算数提前转成浮点,保证除法是浮点除法 int n_blocks = ceil(static_cast<float>(N_array) / n_threads);
关于你的两个技术疑问
1. 线程块数定义是否合理,是否需要总线程数小于设备CUDA核心总数
修正上述bug之后,按数组长度计算刚好覆盖所有元素的块数是能正常跑的,但完全没必要刻意把总线程数设到小于CUDA核心总数,这个思路是搞混了CUDA的硬件调度逻辑:
- CUDA核心只是最底层的算术执行单元,不是线程的驻留单位。每个SM(流多处理器)能同时驻留的线程数是其CUDA核心数的好几倍:比如RTX3090每个SM有128个FP32核心,单SM最多驻留1536个线程;A100每个SM有64个FP32核心,单SM最多驻留2048个线程。GPU靠零开销的线程上下文切换,用多出来的驻留线程隐藏访存、指令依赖的延迟,这才是GPU高吞吐的核心。如果你真把总线程数压到和CUDA核心数差不多,每个线程占一个核心,只要线程遇到访存等待,核心就直接闲置,性能差一个数量级都不奇怪。
- 你对grid-stride循环的理解有偏差:grid-stride的核心价值是解耦网格配置和问题规模,让你不用每次跟着数组长度调整网格大小,固定一个能跑满硬件的网格配置就能适配任意长度的数组,不是让你刻意把块数设得越少越好。刻意减线程块数会导致SM驻留线程不够,占用率上不去,根本发挥不出grid-stride的优势。
块数设置的通用经验很简单:保证总线程数(块数*每块线程数)至少能让所有SM跑满最优占用率就行,一般每块256线程是跨硬件通用的最优选择之一,块数设到设备SM数量的4~8倍就足够吃满所有硬件资源,剩下的计算交给grid-stride循环处理即可。
2. 总线程数超过CUDA核心数是否会带来非活跃块的性能损耗
你担心的这种损耗完全不存在。
CUDA的线程块调度逻辑非常轻量:
- 核函数启动后所有线程块进入全局待调度队列,哪个SM有空余的寄存器、共享内存、线程槽位,就从队列里取一个块上来跑,直到所有块执行完。
- 待调度的块只是存在队列里,不占任何计算资源,没有额外开销。
两种配置的性能差异只和SM占用率有关:
- 如果块数设太少,SM把所有块都加载完还剩大量空闲资源,占用率上不去,访存延迟藏不住,性能会明显下降;
- 只要块数足够让所有SM跑到最优占用率,之后哪怕再多加几倍、几十倍的块数,性能基本不会有明显波动,多出来的块排队调度的开销和计算耗时比完全可以忽略;
- 反而块数太少的时候,最后一个不满载的尾块的空载开销占比会更高,块数足够多的时候尾块开销占比可以忽略,负载更均衡。
最后提个小优化点:你核函数里定义的threadsInBlock变量没有被使用,直接删掉就行,避免不必要的寄存器占用。
内容的提问来源于stack exchange,提问作者PerroNoob
相关产品推荐
相关产品推荐

