1-我对Grid-Stride循环的这种说法感到有些困惑,“而不是假设线程网格足够大以覆盖整个数据数组,”。我在这里不理解“假设”一词,因为当您将网格块启动到GPU时,您已经在脑海中已经有了所需的线程数(基于数据数组),因此,确切知道如何语法<< >>需要很多块来覆盖整个数组,不是吗?如果是这样,为什么要“承担”? (https://devblogs.nvidia.com/cuda-pro-tip-write-flexible-kernels-grid-stride-loops/#disqus_thread)
2-我已经用nvprof进行了测试,似乎单片内核(按照上面的链接)比grid-stride快一点,因为似乎对于每个线程,grid-stride都要在下面计算另外两个命令,这使得它比单声道更长: int索引= blockIdx.x * blockDim.x + threadIdx.x; int stride = blockDim.x * gridDim.x; 也许我测试的方法不正确,希望您能帮助解释更多。
3-此外,据我所知,线程是您创建的计算,它与CUDA核心(这是物理元素)不同。例如,即使您可能只有256个内核,您还是创建了一个512个线程的网格,但是当您启动网格时,GPU仍会计算整个512个线程(即使使用单片内核)。并不是说256个内核只能计算256个线程,而忽略其他线程。但是,此博客:“ https://alexminnaar.com/2019/08/02/grid-stride-loops.html”的解释方式似乎是线程和核心相同。他说:“ ...请考虑以下情形:您编写了一些可以在GPU上正常工作的CUDA代码,但是其他人尝试使用较旧型号的GPU运行它,而他们遇到了这个问题,因为他们的GPU线程数少于您的GPU ”。由于线程是您创建的,因此某个GPU的线程数怎么可能少于其他GPU?您能帮忙澄清一下吗? 我只是从CUDA开始,因此,如果您能帮助我了解更多信息,我将不胜感激。
答案 0 :(得分:0)
1)他说
可伸缩性和线程重用。通过使用循环,您可以支持任何 问题大小,即使它超过了CUDA设备的最大网格大小 支持。
在撰写本文时,并非每个GPU都具有相同的网格限制。但是如今,所有Nvidia GPU都具有相同的网格大小限制。即使工作负载需要的线程数量超过最大网格支持的数量,也可以使用大步循环版本来计算任何大小的问题。 (而不是假设GPU网格限制可以满足工作负载)
他也说
线程重用可分摊线程创建和销毁成本以及内核在循环之前或之后可能进行的其他任何处理(例如,线程专用或共享数据初始化)。
2)可能是您的编译器在循环之前需要#pragma unroll
,因为您为gpu启动的线程太少了。我不需要。对于循环版本,它大约快10%或几乎相同。 (Quadro K420(仅192条管道),具有64个块大小,1M数据元素,1024个线程循环而简单的1M线程)
1M threads: 0.00698701s (average)
1k threads unroll loop: 0.0066207s (average)
1k threads loop: 0.00667657s (average)
此外,由于无环版本也计算索引,因此它仅是一条额外的命令,而仅1个数学运算的延迟可能隐藏在操作数的全局内存请求延迟之后。
最重要的是,额外的命令只计算一次,在循环中使用多次。但是在无环1M线程内核上,它被计算了100万次,而没有重用,从而导致更多的内存消耗和计算消耗。
3) 1 sm单元具有多个扭曲。 1个经线具有32条或类似的车道。每个通道都是流核心或管道。每个管道可以运行16个或其他数量的cuda线程。这使得线程级并行性承担了指令级并行性的负担。如果产生的线程太少,则内核性能取决于指令级并行性。展开循环是ilp中的优化之一。例如,对于每个cuda管道或流核心,Pascal架构可以有16个cuda线程在运行。因此,如果您在内核中启动大量的cuda线程,管线气泡将由cuda warp调度程序自动修复,并且效率很高。您有多少条cuda管道?您启动了多少个cuda线程?
块数也很重要。
如果生成的块太少,则它们不会使用足够的sm单位。产生1个方块只会使用1个sm单位。例如网格上的1024个线程,块大小为1024,网格中总共1个块。如果您有一个具有多个sm单元的gpu,但是如果仅启动1个块,则gpu将具有空闲的sm单元。