Cuda 指令级并行和线程级并行如何在GPU上工作?

Cuda 指令级并行和线程级并行如何在GPU上工作?,cuda,opencl,nvidia,Cuda,Opencl,Nvidia,假设我试图对数组大小n做一个简单的缩减,比如保持在一个工作单元内。。。比如说添加所有元素。一般的策略似乎是在每个GPU上生成大量的工作项,从而减少树中的项。天真地说,这似乎需要logn步骤,但并不是说第一批线程都是一次性完成的,是吗?他们的日程安排得井井有条 for(int offset = get_local_size(0) / 2; offset > 0; offset >>= 1) { if (local_index < offse

假设我试图对数组大小n做一个简单的缩减,比如保持在一个工作单元内。。。比如说添加所有元素。一般的策略似乎是在每个GPU上生成大量的工作项,从而减少树中的项。天真地说,这似乎需要logn步骤,但并不是说第一批线程都是一次性完成的,是吗?他们的日程安排得井井有条

for(int offset = get_local_size(0) / 2;
      offset > 0;
      offset >>= 1) {
     if (local_index < offset) {
       float other = scratch[local_index + offset];
       float mine = scratch[local_index];
       scratch[local_index] = (mine < other) ? mine : other;
     }
     barrier(CLK_LOCAL_MEM_FENCE);
   }
然后将前32项加在一起。我想那32个线程会一次又一次地启动


如果您不反对放弃OpenCL的通用性,那么当您知道每个周期将触发多少次添加时,为什么还要在树中减少呢?

一个线程不能让GPU保持忙碌。这与说一个线程可以让8核CPU保持忙碌大致相同

为了最大限度地利用计算资源和可用内存带宽,有必要利用整个机器(即可以执行线程的所有可用资源)

对于大多数较新的GPU,通过让线程代码具有多条顺序独立的指令,您当然可以通过指令级并行性来提高性能。但是,您不能将所有这些都放到一个线程中,并期望它提供良好的性能

当您按顺序有两条指令时,如下所示:

scratch[0] += scratch[i+16]
scratch[1] += scratch[i+17]
float other = scratch[local_index + offset];
这对ILP是有好处的,因为这两个操作是完全独立的。但是,由于GPU发出内存事务的方式,第一行代码将参与特定的内存事务,第二行代码必然参与不同的内存事务

当我们一起工作时,一行代码如下:

scratch[0] += scratch[i+16]
scratch[1] += scratch[i+17]
float other = scratch[local_index + offset];
将导致warp的所有成员生成一个请求,但这些请求将全部合并到一个或两个内存事务中。这就是如何实现全带宽利用率的方法

尽管大多数现代GPU都有缓存,缓存往往会在一定程度上弥合这两种方法之间的差距,但它们绝不能弥补让所有warp成员发出一个组合请求与单个warp成员按顺序发出一组请求之间在事务方面的巨大差异


您可能需要了解GPU内存合并。由于您的问题似乎是以OpenCL为中心的,您可能对此感兴趣。

谢谢您的回答!那么我的第二个问题呢?为什么不在一个工作组上启动一定数量的扭曲线程,以确保我始终处于计划线程中?基本上与通常在8核机器上运行8个线程的方式相同。GPU通过具有许多可执行的扭曲来隐藏延迟,因此当一个扭曲暂停时(例如由于内存请求),可以执行其他扭曲。如果你想获得充分的性能,那么只安排一次扭曲也是一个坏主意,因为一旦扭曲停止,机器将无所事事,直到停止被清除。如果其他经纱准备就绪,机器可以保持忙碌状态。这在概念上与CPU多线程编程截然不同。