我得到的是blockDim
,但我遇到问题gridDim. Blockdim
给出了块的大小,但是gridDim
是什么?在互联网上,它表示gridDim.x
给出了x坐标中的块数。
我怎么知道blockDim.x * gridDim.x
给出了什么?
我如何知道x行中有多少gridDim.x
个值?
例如,请考虑以下代码:
int tid = threadIdx.x + blockIdx.x * blockDim.x;
double temp = a[tid];
tid += blockDim.x * gridDim.x;
while (tid < count)
{
if (a[tid] > temp)
{
temp = a[tid];
}
tid += blockDim.x * gridDim.x;
}
我知道tid
以0开头。然后代码有tid+=blockDim.x * gridDim.x
。此操作后现在tid
是什么?
答案 0 :(得分:80)
blockDim.x,y,z
给出了一个块中的线程数
特别方向gridDim.x,y,z
给出网格中的块数
特别方向blockDim.x * gridDim.x
给出网格中的线程数(在x方向,在这种情况下)块和网格变量可以是1,2或3维。处理1-D数据时,通常只创建1-D块和网格。
在CUDA文档中,这些变量定义为here
特别是,当x维度(gridDim.x * blockDim.x)中的总线程小于我想要处理的数组的大小时,那么通常的做法是创建循环并让线程网格在整个数组中移动。在这种情况下,在处理一个循环迭代之后,每个线程必须移动到下一个未处理的位置,由tid+=blockDim.x*gridDim.x;
给出。实际上,整个线程网格跳过1-D数据数组,网格宽度一次。本主题(有时称为“网格跨越循环”)将在此blog article中进一步讨论。
您可能需要考虑在NVIDIA webinar page上提供一些CUDA介绍性网络研讨会。例如,这两个:
如果你想更好地理解这些概念,那将花费2个小时。
详细介绍了网格跨越循环的一般主题here。
答案 1 :(得分:45)
gridDim:此变量包含网格的尺寸。
blockIdx:此变量包含网格中的块索引。
blockDim:此变量并包含块的尺寸。
threadIdx:该变量包含块内的线程索引。
你似乎对CUDA的线程层次感到有点困惑;简而言之,对于内核,将有1个网格(我总是可视化为3维立方体)。它的每个元素都是一个块,这样声明为dim3 grid(10, 10, 2);
的网格总块数为10 * 10 * 2。反过来,每个块都是一个三维立方体线程。
话虽如此,通常只使用块和网格的x维度,这就像你问题中的代码所做的那样。如果您正在使用1D阵列,这尤其重要。在这种情况下,您的tid+=blockDim.x * gridDim.x
行实际上是网格中每个线程的唯一索引。这是因为您的blockDim.x
将是每个块的大小,而您的gridDim.x
将是块的总数。
因此,如果您启动带参数的内核
dim3 block_dim(128,1,1);
dim3 grid_dim(10,1,1);
kernel<<<grid_dim,block_dim>>>(...);
然后在你的内核中有threadIdx.x + blockIdx.x*blockDim.x
你将有效地拥有:
threadIdx.x range from [0 ~ 128)
blockIdx.x range from [0 ~ 10)
blockDim.x equal to 128
gridDim.x equal to 10
因此,在计算threadIdx.x + blockIdx.x*blockDim.x
时,您将拥有在[0, 128) + 128 * [1, 10)
定义的范围内的值,这意味着您的tid值范围为{0,1,2,...,1279} 。
当您想要将线程映射到任务时,这非常有用,因为这为您内核中的所有线程提供了唯一的标识符。
但是,如果你有
int tid = threadIdx.x + blockIdx.x * blockDim.x;
tid += blockDim.x * gridDim.x;
然后你基本上会:tid = [0, 128) + 128 * [1, 10) + (128 * 10)
,你的tid值范围是{1280,1281,...,2559}
我不确定哪些内容是相关的,但这完全取决于您的应用程序以及如何将线程映射到数据。这种映射对于任何内核启动都是非常重要的,您就是决定如何完成它的人。当您启动内核时,您需要指定网格和块尺寸,并且您必须在内核中强制执行映射到数据。只要您没有超出硬件限制(对于现代卡,每个块最多可以有2 ^ 10个线程,每个网格最多可以有2 ^ 16 - 1个块)
答案 2 :(得分:1)
在这个源代码中,我们甚至有4个thred,内核函数可以访问所有10个数组。怎么样?
#define N 10 //(33*1024)
__global__ void add(int *c){
int tid = threadIdx.x + blockIdx.x * gridDim.x;
if(tid < N)
c[tid] = 1;
while( tid < N)
{
c[tid] = 1;
tid += blockDim.x * gridDim.x;
}
}
int main(void)
{
int c[N];
int *dev_c;
cudaMalloc( (void**)&dev_c, N*sizeof(int) );
for(int i=0; i<N; ++i)
{
c[i] = -1;
}
cudaMemcpy(dev_c, c, N*sizeof(int), cudaMemcpyHostToDevice);
add<<< 2, 2>>>(dev_c);
cudaMemcpy(c, dev_c, N*sizeof(int), cudaMemcpyDeviceToHost );
for(int i=0; i< N; ++i)
{
printf("c[%d] = %d \n" ,i, c[i] );
}
cudaFree( dev_c );
}
为什么我们不创建10个线程()add<<<2,5>>> or add<5,2>>>
因为我们必须创建相当少数量的线程,如果N大于10 ex)33 * 1024。
此源代码是此案例的示例。 数组是10,cuda线程是4。 如何仅通过4个线程访问所有10个数组。
在cuda详细信息中查看有关threadIdx,blockIdx,blockDim,gridDim含义的页面。
在此源代码中,
gridDim.x : 2 this means number of block of x
gridDim.y : 1 this means number of block of y
blockDim.x : 2 this means number of thread of x in a block
blockDim.y : 1 this means number of thread of y in a block
我们的线程数是4,因为2 * 2(块*线程)。
在添加内核函数时,我们可以访问0,1,2,3的线程索引
- &GT; tid = threadIdx.x + blockIdx.x * blockDim.x
①0+ 0×2 = 0
②1+ 0 * 2 = 1
③0+ 1 * 2 = 2
④1+ 1 * 2 = 3
如何访问索引4,5,6,7,8,9的其余部分。 while循环中有一个计算
tid += blockDim.x + gridDim.x in while
**第一次调用内核**
-1 loop:0 + 2 * 2 = 4
-2循环:4 + 2 * 2 = 8
-3 loop:8 + 2 * 2 = 12(但这个值为false,而out!)
**内核的第二次调用**
-1循环:1 + 2 * 2 = 5
-2循环:5 + 2 * 2 = 9
-3 loop:9 + 2 * 2 = 13(但这个值为false,而out!)
**内核的第三次调用**
-1循环:2 + 2 * 2 = 6
-2循环:6 + 2 * 2 = 10(但是这个值是假的,而在外面!)
**内核的第四次调用**
-1 loop:3 + 2 * 2 = 7
-2 loop:7 + 2 * 2 = 11(但这个值为false,而out!)
因此,所有0,1,2,3,4,5,6,7,8,9的索引都可以通过tid值访问。
参考此页面。 http://study.marearts.com/2015/03/to-process-all-arrays-by-reasonably.html 我无法上传图片,因为信誉很低。
答案 3 :(得分:0)
首先,请参见Grid of thread blocks中的图CUDA official document
通常,我们使用内核:
__global__ void kernelname(...){
const id_x = blockDim.x * blockIdx.x + threadIdx.x;
const id_y = blockDim.y * blockIdx.y + threadIdx.y;
...
}
// invoke kernel
// assume we have assigned the proper gridsize and blocksize
kernelname<<<gridsize, blocksize>>>(...)
一些变量的含义:
gridsize
每个网格的块数,对应于gridDim
blocksize
每个块的线程数,对应于blockDim
threadIdx.x
的不同之处在于[0,blockDim.x)
blockIdx.x
的不同之处在于[0,gridDim.x)
因此,当我们有threadIdx.x
和blockIdx.x
时,让我们尝试在 x方向上计算索引。根据{{3}},blockIdx.x
确定您是哪个块,而threadIdx.x
确定给出块的位置时您是哪个线程。因此,我们有:
which_blk = blockDim.x * blockIdx.x; // which block you are
final_index_x = which_blk + threadIdx.x; // based on the given block, we can have the final location by adding the threadIdx.x
即:
final_index_x = blockDim.x * blockIdx.x + threadIdx.x;
与上面的示例代码相同。
类似地,我们可以分别在 y或z方向获得索引。
如我们所见,我们通常在代码中不使用gridDim
,因为此信息是在blockIdx
的范围内执行的。相反,尽管此信息作为blockDim
的范围执行,但我们必须使用threadIdx
。我上面一步一步显示的原因。
我希望这个答案可以解决您的困惑。