早上好。
我开始学习cuda编程,我正在考虑表现。我在CUDA网站上读到,在其他方面表现良好,我们应该考虑以下四个方面:
- 每个SM(系统多处理器) 每SM的块数 - 每个SM注册 - 每个SM的共享内存
所以我正在讨论第一件事情,根据GPU,我根据每个SM的最大warp和每个SM的块来定义内核的尺寸。我的任务是执行十亿的总和来衡量哪种方法更好。
我所做的是一个for循环,我在每次迭代时启动一个最大化占用率的内核。例如,对于NVidia 1080 GPU,我读到了:
int max_blocks = 32; //maximum number of active blocks per SM
int max_threads_per_Block = 64; //maximum number of active threads per SM
int max_threads = 2048;
每个SM总共提供2048个线程,并保证最大占用率。这个GPU可以有64个活动warp,每个warrt有32个线程。在这个GPU中,一个活动块有2个warp,这意味着每个块一次可以有64个活动线程。有了这个,我按如下方式启动内核:
dim3 threadsPerBlock(max_threads_per_Block);
dim3 numBlocks(max_blocks);
VecAdd<<<numBlocks, threadsPerBlock>>>(d_A, d_B, d_C,max_threads);
我惊讶地注意到,如果我直接启动此内核,如:
int N = total_ops; //in this case one thousand millions
dim3 threadsPerBlock(256);
dim3 numBlocks(2*N / threadsPerBlock.x);
VecAdd<<<numBlocks, threadsPerBlock>>>(d_A, d_B, d_C,);
性能更好(消耗时间)。我在相同的执行中启动相同的实验5次以避免异常值。我的问题是:有没有办法管理占用率以获得比编译器和运行时API更好的结果?我知道我尝试做的优化已经以某种方式由GPU管理。我理解,如果有一份文件解释我们应该如何启动软件(上面的链接)以获得良好的性能,那么它应该是一种控制它的方法。
谢谢
答案 0 :(得分:2)
在你的第一个例子中,
int max_blocks = 32; //maximum number of active blocks per SM
int max_threads_per_Block = 64; //maximum number of active threads per SM
int max_threads = 2048;
dim3 threadsPerBlock(max_threads_per_Block);
dim3 numBlocks(max_blocks);
VecAdd<<<numBlocks, threadsPerBlock>>>(d_A, d_B, d_C,max_threads);
您需要根据需要为每个块启动尽可能多的块和线程,以完全加载一个 SM。但是,您的GTX 1080有 20 SM,因此您的入住率仅为1/20 = 5%。
在第二个例子中,
int N = total_ops; //in this case one thousand millions
dim3 threadsPerBlock(256);
dim3 numBlocks(2*N / threadsPerBlock.x);
VecAdd<<<numBlocks, threadsPerBlock>>>(d_A, d_B, d_C,);
您正在启动大量的块,这允许GPU根据需要并行执行多达100%的占用率(资源允许,在简单的向量添加的情况下这不应该是一个问题)。因此表现更好。
虽然您可以在第一个示例中将块数乘以20以获得与第二个示例相同的性能,但第二个示例中的模式是首选,因为它不涉及所使用的GPU的特定配置。因此代码将完全加载任何大范围的GPU。
另一方面,作为存储器绑定算法的向量加法并不特别适合于证明占用的效果。但是,您仍然看到差异,因为完全加载内存子系统需要一定的最小内存事务数量(由内存带宽乘以内存访问的延迟确定),并且5%的占用率示例低于此最小值