CUDA编程- - GPU线程的理解 thread,block,grid - 再次学习

GPU线程的理解 thread,block,grid

  • 一、从 cpu 多线程角度理解 gpu 多线程
    • 1、cpu 多线程并行加速
    • 2、gpu多线程并行加速
      • 2.1、cpu 线程与 gpu 线程的理解(核函数)
        • 2.1.1 、第一步:编写核函数
        • 2.1.2、第二步:调用核函数(使用内核函数)
        • 2.1.3、第三步:编写 CMakeLists & 编译代码
  • 二、重要概念 & 与线程索引的直观理解
    • 2.1、重要概念
    • 2.2、dim3与启动内核
    • 2.3、如何找到线程块的索引
    • 2.4、如何找到绝对线程索引
  • 三、参考代码(打印索引)
    • 3.1、打印一维索引
    • 3.2、打印二维索引
    • 3.2、扩展应用 (获取图片坐标)

一、从 cpu 多线程角度理解 gpu 多线程

1、cpu 多线程并行加速

在 cpu 中,用 openmp 并行计算,有限的线程数对 128 进行分组运算。

#pragma omp parallel for
for(int i =0;i<128;i++)
{
	a[i]=b[i]*c[i];
}

2、gpu多线程并行加速

在 gpu 中,可以直接开启 128 个线程对其进行计算。下面步骤和代码是演示如何开启 128个线程并打印

2.1、cpu 线程与 gpu 线程的理解(核函数)

2.1.1 、第一步:编写核函数
__global__ void some_kernel_func(int *a, int *b, int *c)
{
    // 初始化线程ID
    int i = (blockIdx.x * blockDim.x) + threadIdx.x;
    // 对数组元素进行乘法运算
    a[i] = b[i] * c[i];
    // 打印打前处理的进程ID
    // 可以看到blockIdx并非是按照顺序启动的,这也说明线程块启动的随机性
    printf("blockIdx.x = %d,blockDimx.x = %d,threadIdx.x = %d\n", blockIdx.x, blockDim.x, threadIdx.x);
}
2.1.2、第二步:调用核函数(使用内核函数)

#)调用语法
kernel_function<<<num_blocks,num_threads>>>(param1,param2,...)

  • num_blocks 线程块,至少保证一个线程块
  • num_threads 执行内核函数的线程数量

#)tips:

1、 some_kernel_func<<<1,128>>>(a,b,c); 调用 some_kernel_func 1*128 次

2、 some_kernel_func<<<2,128>>>(a,b,c); 调用 some_kernel_func 2*128 次

3、如果将 num_blocks1 改成 2 ,则表示 gpu 将启动两倍于之前的线程数量的线程,

在 blockIdx.x = 0 中,i = threadIdx.x
在 blockIdx.x = 1 中, blockDim.x 表示所要求每个线程块启动的线程数量,在这 = 128

2.1.3、第三步:编写 CMakeLists & 编译代码

CMakeLists.txt

cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
project(demo)
add_definitions(-std=c++14)      
find_package(CUDA REQUIRED)

# add cuda
include_directories(${CUDA_INCLUDE_DIRS} ) 
message("CUDA_LIBRARIES:${CUDA_LIBRARIES}")
message("CUDA_INCLUDE_DIRS:${CUDA_INCLUDE_DIRS}")
cuda_add_executable(demo print_theardId.cu)

# link
target_link_libraries (demo ${CUDA_LIBRARIES})

print_theardId.cu

#include <stdio.h>
#include <stdlib.h>

#include <cuda.h>
#include <cuda_runtime.h>

/*gpu 中的矩阵乘法*/
__global__ void some_kernel_func(int *a, int *b, int *c)
{
    // 初始化线程ID
    int i = (blockIdx.x * blockDim.x) + threadIdx.x;
    // 对数组元素进行乘法运算
    a[i] = b[i] * c[i];
    // 打印打前处理的进程ID
    // 可以看到blockIdx并非是按照顺序启动的,这也说明线程块启动的随机性
    printf("blockIdx.x = %d,blockDimx.x = %d,threadIdx.x = %d\n", blockIdx.x, blockDim.x, threadIdx.x);
}

int main(void)
{
    // 初始化指针元素
    int *a, *b, *c;
    // 初始化GPU指针元素
    int *gpu_a, *gpu_b, *gpu_c;
    // 初始化数组大小
    int size = 128 * sizeof(int);
    // 为CPU指针元素分配内存
    a = (int *)malloc(size);
    b = (int *)malloc(size);
    c = (int *)malloc(size);
    // 为GPU指针元素分配内存
    cudaMalloc((void **)&gpu_a, size);
    cudaMalloc((void **)&gpu_b, size);
    cudaMalloc((void **)&gpu_c, size);
    // 初始化数组元素
    for (int i = 0; i < 128; i++)
    {
        b[i] = i;
        c[i] = i;
    }
    // 将数组元素复制到GPU中
    cudaMemcpy(gpu_b, b, size, cudaMemcpyHostToDevice);
    cudaMemcpy(gpu_c, c, size, cudaMemcpyHostToDevice);
    // 执行GPU核函数
    some_kernel_func<<<4, 32>>>(gpu_a, gpu_b, gpu_c);
    // 将GPU中的结果复制到CPU中
    cudaMemcpy(a, gpu_a, size, cudaMemcpyDeviceToHost);
    // 释放GPU和CPU中的内存
    cudaFree(gpu_a);
    cudaFree(gpu_b);
    cudaFree(gpu_c);
    free(a);
    free(b);
    free(c);
    return 0;
}
mkdir build
cd build
cmake ..
make
./demo

部分结果:
在这里插入图片描述

可以看到,
1、gpu 可以直接调用很多个线程,
2、线程数量的多少是由线程块,线程,线程网格等决定的,
3、在核函数中编写单个线程的使用代码,再调用核函数,便可简单的达到 cpu 中 openmp 的多线程方式

二、重要概念 & 与线程索引的直观理解

2.1、重要概念

在这里插入图片描述

gridDim.x – 线程网格X维度的线程块数目
gridDim.y – 线程网格Y维度的线程块数目

blockDim.x – 一个线程块X维度上的线程数量
blockDim.y – 一个线程块Y维度上的线程数量

theadIdx.x – 线程块X维度上的线程数量
theadIdx.y – 线程块Y维度上的线程数量

一般来说:
一个 kernel 对应一个 grid
一个 grid 可以有多个 block,一维~三维
一个 block 可以有多个 thread,一维~三维

2.2、dim3与启动内核

dim3 是CUDA中的特殊数据结构,可用来创建二维的线程块与线程网络
eg:4个线程块,128个线程

dim3 threads_rect(32,4)	// 每个线程块在X方向开启32个线程,Y方向开启4个线程
dim3 blocks_rect(1,4)	//在线程网格上,x方向1个线程块,Y方向4个线程
or
dim3 threads_square(16,8)
dim3 blocks_square(2,2)

以上两种方式线程数都是 324=128 , 168=128,只是线程块中线程的排布方式不一样

启动内核
1、 some_kernel_func<<<blocks_rect,threads_rect>>>(a,b,c);

2、 some_kernel_func<<<blocks_square,threads_square>>>(a,b,c);

2.3、如何找到线程块的索引

线程块的索引 x 线程块的大小 + 线程数量的起始点
参考核函数

// 定义ID查询函数
__global__ void what_is_my_id(
    unsigned int *const block,
    unsigned int *const thread,
    unsigned int *const warp,
    unsigned int *const calc_thread)
{
    /*线程ID是线程块的索引 x 线程块的大小 + 线程数量的起始点*/
    const unsigned int thread_idx = (blockIdx.x * blockDim.x) + threadIdx.x;

    block[thread_idx] = blockIdx.x;
    thread[thread_idx] = threadIdx.x;

    /*线程束 = 线程ID / 内置变量warpSize*/
    warp[thread_idx] = thread_idx / warpSize;

    calc_thread[thread_idx] = thread_idx;
}

来个.cu文件,体验一下这个核函数,// 编译方法同上

#include <stdio.h>
#include <stdlib.h>

#include "cuda.h"
#include "cuda_runtime.h"

// 定义ID查询函数
__global__ void what_is_my_id(
    unsigned int *const block,
    unsigned int *const thread,
    unsigned int *const warp,
    unsigned int *const calc_thread)
{
    /*线程ID是线程块的索引 x 线程块的大小 + 线程数量的起始点*/
    const unsigned int thread_idx = (blockIdx.x * blockDim.x) + threadIdx.x;

    block[thread_idx] = blockIdx.x;
    thread[thread_idx] = threadIdx.x;

    /*线程束 = 线程ID / 内置变量warpSize*/
    warp[thread_idx] = thread_idx / warpSize;

    calc_thread[thread_idx] = thread_idx;
}

// 定义数组大小
#define ARRAY_SIZE 1024
// 定义数组字节大小
#define ARRAY_BYTES ARRAY_SIZE * sizeof(unsigned int)

// 声明主机下参数
unsigned int cpu_block[ARRAY_SIZE];
unsigned int cpu_thread[ARRAY_SIZE];
unsigned int cpu_warp[ARRAY_SIZE];
unsigned int cpu_calc_thread[ARRAY_SIZE];

// 定义主函数
int main(void)
{
    // 总线程数量为 2 x 64 = 128
    // 初始化线程块和线程数量
    const unsigned int num_blocks = 2;
    const unsigned int num_threads = 64;
    char ch;

    // 声明设备下参数
    unsigned int *gpu_block, *gpu_thread, *gpu_warp, *gpu_calc_thread;

    // 声明循环数量
    unsigned int i;

    // 为设备下参数分配内存
    cudaMalloc((void **)&gpu_block, ARRAY_BYTES);
    cudaMalloc((void **)&gpu_thread, ARRAY_BYTES);
    cudaMalloc((void **)&gpu_warp, ARRAY_BYTES);
    cudaMalloc((void **)&gpu_calc_thread, ARRAY_BYTES);

    // 调用核函数
    what_is_my_id<<<num_blocks, num_threads>>>(gpu_block, gpu_thread, gpu_warp, gpu_calc_thread);

    // 将设备下参数复制到主机下
    cudaMemcpy(cpu_block, gpu_block, ARRAY_BYTES, cudaMemcpyDeviceToHost);
    cudaMemcpy(cpu_thread, gpu_thread, ARRAY_BYTES, cudaMemcpyDeviceToHost);
    cudaMemcpy(cpu_warp, gpu_warp, ARRAY_BYTES, cudaMemcpyDeviceToHost);
    cudaMemcpy(cpu_calc_thread, gpu_calc_thread, ARRAY_BYTES, cudaMemcpyDeviceToHost);

    // 释放GPU内存
    cudaFree(gpu_block);
    cudaFree(gpu_thread);
    cudaFree(gpu_warp);
    cudaFree(gpu_calc_thread);

    // 循环打印结果
    for (i = 0; i < ARRAY_SIZE; i++)
    {
        printf("Calculated Thread: %d - Block: %d - Warp: %d - Thread: %d\n", cpu_calc_thread[i], cpu_block[i], cpu_warp[i], cpu_thread[i]);
    }
    return 0;
}

2.4、如何找到绝对线程索引

thread_idx = ( (gridDim.x * blockDim.x ) * idy ) + idx;

绝对线程索引 = 当前行索引 * 每行线程总数 + x方向的偏移
参考核函数

/*定义线程id计算函数*/
__global__ void what_is_my_id_2d_A(
    unsigned int *const block_x,
    unsigned int *const block_y,
    unsigned int *const thread,
    unsigned int *const calc_thread,
    unsigned int *const x_thread,
    unsigned int *const y_thread,
    unsigned int *const grid_dimx,
    unsigned int *const block_dimx,
    unsigned int *const grid_dimy,
    unsigned int *const block_dimy)
{
    /*获得线程索引*/
    const unsigned int idx = (blockIdx.x * blockDim.x) + threadIdx.x;
    const unsigned int idy = (blockIdx.y * blockDim.y) + threadIdx.y;
    /*
        计算线程id
        计算公式:线程ID = ((网格维度x * 块维度x) * 线程idy) + 线程idx(作为x维度上的偏移)
    */
    const unsigned int thread_idx = ((gridDim.x * blockDim.x) * idy) + idx;
    /*获取线程块的索引*/
    block_x[thread_idx] = blockIdx.x;
    block_y[thread_idx] = blockIdx.y;
    /*获取线程的索引*/
    thread[thread_idx] = threadIdx.x;
    /*计算线程id*/
    calc_thread[thread_idx] = thread_idx;
    /*获取线程的x维度索引*/
    x_thread[thread_idx] = idx;
    /*获取线程的y维度索引*/
    y_thread[thread_idx] = idy;
    /*获取网格维度的X,Y值*/
    grid_dimx[thread_idx] = gridDim.x;
    grid_dimy[thread_idx] = gridDim.y;
    /*获取block_dimy*/
    block_dimx[thread_idx] = blockDim.x;
}

来个.cu文件,体验一下这个核函数,// 编译方法同上

#include <stdio.h>
#include <stdlib.h>
#include <cuda.h>
#include <cuda_runtime.h>

/*定义线程id计算函数*/
__global__ void what_is_my_id_2d_A(
    unsigned int *const block_x,
    unsigned int *const block_y,
    unsigned int *const thread,
    unsigned int *const calc_thread,
    unsigned int *const x_thread,
    unsigned int *const y_thread,
    unsigned int *const grid_dimx,
    unsigned int *const block_dimx,
    unsigned int *const grid_dimy,
    unsigned int *const block_dimy)
{
    /*获得线程索引*/
    const unsigned int idx = (blockIdx.x * blockDim.x) + threadIdx.x;
    const unsigned int idy = (blockIdx.y * blockDim.y) + threadIdx.y;
    /*
        计算线程id
        计算公式:线程ID = ((网格维度x * 块维度x) * 线程idy) + 线程idx(作为x维度上的偏移)
    */
    const unsigned int thread_idx = ((gridDim.x * blockDim.x) * idy) + idx;
    /*获取线程块的索引*/
    block_x[thread_idx] = blockIdx.x;
    block_y[thread_idx] = blockIdx.y;
    /*获取线程的索引*/
    thread[thread_idx] = threadIdx.x;
    /*计算线程id*/
    calc_thread[thread_idx] = thread_idx;
    /*获取线程的x维度索引*/
    x_thread[thread_idx] = idx;
    /*获取线程的y维度索引*/
    y_thread[thread_idx] = idy;
    /*获取网格维度的X,Y值*/
    grid_dimx[thread_idx] = gridDim.x;
    grid_dimy[thread_idx] = gridDim.y;
    /*获取block_dimy*/
    block_dimx[thread_idx] = blockDim.x;
}

/*定义矩阵宽度以及大小*/
#define ARRAY_SIZE_X 32
#define ARRAY_SIZE_Y 16
#define ARRAY_SIZE_IN_BYTES (ARRAY_SIZE_X * ARRAY_SIZE_Y * sizeof(unsigned int))

/*声明CPU端上的各项参数内存*/
unsigned int *cpu_block_x[ARRAY_SIZE_Y][ARRAY_SIZE_X];
unsigned int *cpu_block_y[ARRAY_SIZE_Y][ARRAY_SIZE_X];
unsigned int *cpu_thread[ARRAY_SIZE_Y][ARRAY_SIZE_X];
unsigned int *cpu_warp[ARRAY_SIZE_Y][ARRAY_SIZE_X];
unsigned int *cpu_calc_thread[ARRAY_SIZE_Y][ARRAY_SIZE_X];
unsigned int *cpu_x_thread[ARRAY_SIZE_Y][ARRAY_SIZE_X];
unsigned int *cpu_y_thread[ARRAY_SIZE_Y][ARRAY_SIZE_X];
unsigned int *cpu_grid_dimx[ARRAY_SIZE_Y][ARRAY_SIZE_X];
unsigned int *cpu_grid_dimy[ARRAY_SIZE_Y][ARRAY_SIZE_X];
unsigned int *cpu_block_dimx[ARRAY_SIZE_Y][ARRAY_SIZE_X];
unsigned int *cpu_block_dimy[ARRAY_SIZE_Y][ARRAY_SIZE_X];

int main(void)
{
   const dim3 thread_rect = (32, 4);
    /*注意这里的块的dim3值为1x4*/
    const dim3 block_rect = (1, 4);

    /*初始化矩形线程分布启动项*/
     const dim3 thread_square = (16, 8);
    /*注意这里的块的dim3值为2x2*/
    const dim3 block_square = (2, 2);

    /*定义一个临时指针用于打印信息*/
    char ch;

    /*定义GPU端上的各项参数内存*/
    unsigned int *gpu_block_x;
    unsigned int *gpu_block_y;
    unsigned int *gpu_thread;
    unsigned int *gpu_warp;
    unsigned int *gpu_calc_thread;
    unsigned int *gpu_x_thread;
    unsigned int *gpu_y_thread;
    unsigned int *gpu_grid_dimx;
    unsigned int *gpu_grid_dimy;
    unsigned int *gpu_block_dimx;

    /*分配GPU端上的各项参数内存*/
    cudaMalloc((void **)&gpu_block_x, ARRAY_SIZE_IN_BYTES);
    cudaMalloc((void **)&gpu_block_y, ARRAY_SIZE_IN_BYTES);
    cudaMalloc((void **)&gpu_thread, ARRAY_SIZE_IN_BYTES);
    cudaMalloc((void **)&gpu_warp, ARRAY_SIZE_IN_BYTES);
    cudaMalloc((void **)&gpu_calc_thread, ARRAY_SIZE_IN_BYTES);
    cudaMalloc((void **)&gpu_x_thread, ARRAY_SIZE_IN_BYTES);
    cudaMalloc((void **)&gpu_y_thread, ARRAY_SIZE_IN_BYTES);
    cudaMalloc((void **)&gpu_grid_dimx, ARRAY_SIZE_IN_BYTES);
    cudaMalloc((void **)&gpu_grid_dimy, ARRAY_SIZE_IN_BYTES);
    cudaMalloc((void **)&gpu_block_dimx, ARRAY_SIZE_IN_BYTES);

    /*调用核函数*/
    for (int kernel = 0; kernel < 2; kernel++)
    {
        switch (kernel)
        {
        case 0:
            /*执行矩形配置核函数*/
            what_is_my_id_2d_A<<<block_rect, thread_rect>>>(gpu_block_x, gpu_block_y, gpu_thread, gpu_warp, gpu_calc_thread, gpu_x_thread, gpu_y_thread, gpu_grid_dimx, gpu_grid_dimy, gpu_block_dimx);
            break;
        case 1:
            /*执行方形配置核函数*/
            what_is_my_id_2d_A<<<block_square, thread_square>>>(gpu_block_x, gpu_block_y, gpu_thread, gpu_warp, gpu_calc_thread, gpu_x_thread, gpu_y_thread, gpu_grid_dimx, gpu_grid_dimy, gpu_block_dimx);
            break;
        default:
            exit(1);
            break;
        }

        /*将GPU端上的各项参数内存拷贝到CPU端上*/
        cudaMemcpy(cpu_block_x, gpu_block_x, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost);
        cudaMemcpy(cpu_block_y, gpu_block_y, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost);
        cudaMemcpy(cpu_thread, gpu_thread, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost);
        cudaMemcpy(cpu_warp, gpu_warp, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost);
        cudaMemcpy(cpu_calc_thread, gpu_calc_thread, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost);
        cudaMemcpy(cpu_x_thread, gpu_x_thread, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost);
        cudaMemcpy(cpu_y_thread, gpu_y_thread, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost);
        cudaMemcpy(cpu_grid_dimx, gpu_grid_dimx, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost);
        cudaMemcpy(cpu_grid_dimy, gpu_grid_dimy, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost);
        cudaMemcpy(cpu_block_dimx, gpu_block_dimx, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost);

        printf("\n kernel %d\n", kernel);
        /*打印结果*/
        for (int y = 0; y < ARRAY_SIZE_Y; y++)
        {
            for (int x = 0; x < ARRAY_SIZE_X; x++)
            {
                printf("CT: %2u Bkx: %1u TID: %2u YTID: %2u XTID: %2u GDX: %1u BDX: %1u GDY: %1u BDY:%1U\n", cpu_calc_thread[y * ARRAY_SIZE_X + x], cpu_block_x[y * ARRAY_SIZE_X + x], cpu_thread[y * ARRAY_SIZE_X + x], cpu_y_thread[y * ARRAY_SIZE_X + x], cpu_x_thread[y * ARRAY_SIZE_X + x], cpu_grid_dimx[y * ARRAY_SIZE_X + x], cpu_block_dimx[y * ARRAY_SIZE_X + x], cpu_grid_dimy[y * ARRAY_SIZE_X + x], cpu_block_y[y * ARRAY_SIZE_X + x]);
            }
            /*每行打印完后按任意键继续*/
            ch = getchar();
        }
        printf("Press any key to continue\n");
        ch = getchar();
    }
    /*释放GPU端上的各项参数内存*/
    cudaFree(gpu_block_x);
    cudaFree(gpu_block_y);
    cudaFree(gpu_thread);
    cudaFree(gpu_warp);
    cudaFree(gpu_calc_thread);
    cudaFree(gpu_x_thread);
    cudaFree(gpu_y_thread);
    cudaFree(gpu_grid_dimx);
    cudaFree(gpu_grid_dimy);
    cudaFree(gpu_block_dimx);
}

其中有个代码片段

   const dim3 thread_rect = (32, 4);
    /*注意这里的块的dim3值为1x4*/
    const dim3 block_rect = (1, 4);
    
     const dim3 thread_square = (16, 8);
    /*注意这里的块的dim3值为2x2*/
    const dim3 block_square = (2, 2);

如图理解,都是 2x2 / 1x4 = 四个线程块;每一块 32x4 / 16x8 =128个线程。这是两种不同的线程块布局方式。

但是一般会选择长方形的布局方式。
1、要以行的方式进行连续访问内存,而不是列的方式
2、同一个线程块可以通过共享内存进行通信
3、同一个线程束中的线程存储访问合并在一起了,长方形布局只需要一次访问操作就可以获得连续的内存数据 // 正方形要两次访问

在这里插入图片描述

三、参考代码(打印索引)

3.1、打印一维索引

场景:
一个数组有 8 个数据,要开 8 个线程去访问。
我们想切成 2 个block 访问,所以一个 blcok 就有 4 个线程

在这里插入图片描述
所以 线程设置如下:一个 block里面4个线程,一个grid里面2个block

一维索引的设置如下:

    dim3 block(4);// 一个 block 里面 4 个线程
    dim3 grid(2);//	一个 grid 里面 2 个 block	
#include <cuda_runtime.h>
#include <stdio.h>

__global__ void print_idx_kernel(){
    printf("block idx: (%3d, %3d, %3d), thread idx: (%3d, %3d, %3d)\n",
         blockIdx.z, blockIdx.y, blockIdx.x,
         threadIdx.z, threadIdx.y, threadIdx.x);
}

__global__ void print_dim_kernel(){
    printf("grid dimension: (%3d, %3d, %3d), block dimension: (%3d, %3d, %3d)\n",
         gridDim.z, gridDim.y, gridDim.x,
         blockDim.z, blockDim.y, blockDim.x);
}

__global__ void print_thread_idx_per_block_kernel(){
    int index = threadIdx.z * blockDim.x * blockDim.y + \
              threadIdx.y * blockDim.x + \
              threadIdx.x;

    printf("block idx: (%3d, %3d, %3d), thread idx: %3d\n",
         blockIdx.z, blockIdx.y, blockIdx.x,
         index);
}

__global__ void print_thread_idx_per_grid_kernel(){
    int bSize  = blockDim.z * blockDim.y * blockDim.x;

    int bIndex = blockIdx.z * gridDim.x * gridDim.y + \
               blockIdx.y * gridDim.x + \
               blockIdx.x;

    int tIndex = threadIdx.z * blockDim.x * blockDim.y + \
               threadIdx.y * blockDim.x + \
               threadIdx.x;

    int index  = bIndex * bSize + tIndex;

    printf("block idx: %3d, thread idx in block: %3d, thread idx: %3d\n", 
         bIndex, tIndex, index);
}

void print_one_dim(){
    int inputSize = 8;
    int blockDim = 4;
    int gridDim = inputSize / blockDim;

    dim3 block(blockDim);//4
    dim3 grid(gridDim);//2

    print_idx_kernel<<<grid, block>>>();
    //print_dim_kernel<<<grid, block>>>();
    //print_thread_idx_per_block_kernel<<<grid, block>>>();
    //print_thread_idx_per_grid_kernel<<<grid, block>>>();

    cudaDeviceSynchronize();	//用于同步
}

int main() {
    print_one_dim();
    return 0;
}

核函数及其结果:

  • 8个线程,8个输出;
  • 索引都是从 z到y到x的;

1、线程块与线程

__global__ void print_idx_kernel(){
    printf("block idx: (%3d, %3d, %3d), thread idx: (%3d, %3d, %3d)\n",
         blockIdx.z, blockIdx.y, blockIdx.x,
         threadIdx.z, threadIdx.y, threadIdx.x);
}

在这里插入图片描述

2、线程网格与线程块

__global__ void print_dim_kernel(){
    printf("grid dimension: (%3d, %3d, %3d), block dimension: (%3d, %3d, %3d)\n",
         gridDim.z, gridDim.y, gridDim.x,
         blockDim.z, blockDim.y, blockDim.x);
}

1x1x2=2
1x1x4=4

在这里插入图片描述

3、在 block 里面寻找每个线程的索引

__global__ void print_thread_idx_per_block_kernel(){
    int index = threadIdx.z * blockDim.x * blockDim.y + \
              threadIdx.y * blockDim.x + \
              threadIdx.x;

    printf("block idx: (%3d, %3d, %3d), thread idx: %3d\n",
         blockIdx.z, blockIdx.y, blockIdx.x,
         index);
}

可以根据下面的图来理解访问顺序:

在这里插入图片描述

结果:
在这里插入图片描述
4、在 grid 里面寻找每个线程索引

__global__ void print_thread_idx_per_grid_kernel(){
    int bSize  = blockDim.z * blockDim.y * blockDim.x;	// block 的线程大小

    int bIndex = blockIdx.z * gridDim.x * gridDim.y + \
               blockIdx.y * gridDim.x + \
               blockIdx.x;

    int tIndex = threadIdx.z * blockDim.x * blockDim.y + \
               threadIdx.y * blockDim.x + \
               threadIdx.x;

    int index  = bIndex * bSize + tIndex;

    printf("block idx: %3d, thread idx in block: %3d, thread idx: %3d\n", 
         bIndex, tIndex, index);
}

可以根据下面的图来理解访问顺序:实际上就是从一堆方块里面找到那个红点

在这里插入图片描述
结果:(thread 从 0 ~ 7 )
在这里插入图片描述

3.2、打印二维索引

#include <cuda_runtime.h>
#include <stdio.h>


__global__ void print_idx_kernel(){
    printf("block idx: (%3d, %3d, %3d), thread idx: (%3d, %3d, %3d)\n",
         blockIdx.z, blockIdx.y, blockIdx.x,
         threadIdx.z, threadIdx.y, threadIdx.x);
}

__global__ void print_dim_kernel(){
    printf("grid dimension: (%3d, %3d, %3d), block dimension: (%3d, %3d, %3d)\n",
         gridDim.z, gridDim.y, gridDim.x,
         blockDim.z, blockDim.y, blockDim.x);
}

__global__ void print_thread_idx_per_block_kernel(){
    int index = threadIdx.z * blockDim.x * blockDim.y + \
              threadIdx.y * blockDim.x + \
              threadIdx.x;

    printf("block idx: (%3d, %3d, %3d), thread idx: %3d\n",
         blockIdx.z, blockIdx.y, blockIdx.x,
         index);
}

__global__ void print_thread_idx_per_grid_kernel(){
    int bSize  = blockDim.z * blockDim.y * blockDim.x;

    int bIndex = blockIdx.z * gridDim.x * gridDim.y + \
               blockIdx.y * gridDim.x + \
               blockIdx.x;

    int tIndex = threadIdx.z * blockDim.x * blockDim.y + \
               threadIdx.y * blockDim.x + \
               threadIdx.x;

    int index  = bIndex * bSize + tIndex;

    printf("block idx: %3d, thread idx in block: %3d, thread idx: %3d\n", 
         bIndex, tIndex, index);
}


void print_two_dim(){
    int inputWidth = 4;

    int blockDim = 2;
    int gridDim = inputWidth / blockDim;


    dim3 block(blockDim, blockDim);// 2 , 2
    dim3 grid(gridDim, gridDim);    //2,2


    print_idx_kernel<<<grid, block>>>();
    // print_dim_kernel<<<grid, block>>>();
    // print_thread_idx_per_block_kernel<<<grid, block>>>();
    //print_thread_idx_per_grid_kernel<<<grid, block>>>();

    cudaDeviceSynchronize();
}

int main() {
     print_two_dim();
    return 0;
}

3.2、扩展应用 (获取图片坐标)

原理其实就是同上面(在 grid 里面寻找每个线程索引)一样,这里为了方便看,再次贴一次图。

在这里插入图片描述
重写一个核函数,比之前的方便看

__global__ void print_cord_kernel(){
    int index = threadIdx.z * blockDim.x * blockDim.y + \
              threadIdx.y * blockDim.x + \
              threadIdx.x;

    int x  = blockIdx.x * blockDim.x + threadIdx.x;
    int y  = blockIdx.y * blockDim.y + threadIdx.y;

    printf("block idx: (%3d, %3d, %3d), thread idx: %3d, cord: (%3d, %3d)\n",
         blockIdx.z, blockIdx.y, blockIdx.x,
         index, x, y);
}

完整的 .cu 文件如下:

#include <cuda_runtime.h>
#include <stdio.h>

__global__ void print_cord_kernel(){
    int index = threadIdx.z * blockDim.x * blockDim.y + \
              threadIdx.y * blockDim.x + \
              threadIdx.x;

    int x  = blockIdx.x * blockDim.x + threadIdx.x;
    int y  = blockIdx.y * blockDim.y + threadIdx.y;

    printf("block idx: (%3d, %3d, %3d), thread idx: %3d, cord: (%3d, %3d)\n",
         blockIdx.z, blockIdx.y, blockIdx.x,
         index, x, y);
}

void print_cord(){
    int inputWidth = 4;

    int blockDim = 2;
    int gridDim = inputWidth / blockDim;

    dim3 block(blockDim, blockDim);
    dim3 grid(gridDim, gridDim);

    print_cord_kernel<<<grid, block>>>();

    cudaDeviceSynchronize();
}

int main() {
     print_cord();
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/352979.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用多时相的遥感影像进行自动分类的开源模型

简介 Prithvi是NASA 和 IBM 创建的一个用于遥感分类的开源基础模型&#xff0c;使用Landsat 和 Sentinel-2 ( HLS ) 数据集进行训练&#xff0c;特别适合处理时间序列的遥感影像。 该模型已经在2023年的8开源和部署到了Hugging Face Hub上&#xff0c;用户可以免费下载和在线…

【Godot4自学手册】第七节背景搭建

各位同学&#xff0c;今天是第七节&#xff0c;在本节我会学习如何使用TileMap来完成背景搭建。 一、添加TileMap结点 先做个介绍&#xff0c;TileMap是基于 2D 图块的地图节点。Tilemap&#xff08;图块地图&#xff09;使用 TileSet&#xff0c;其中包含了图块的列表&#…

移动Web——平面转换-旋转

1、平面转换-旋转 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style…

asp.net宠物流浪救助系统

asp.net宠物流浪救助系统 当领养人是无或者未领养的时候&#xff0c;就会显示领养申请按钮&#xff0c;登陆的用户可以申请领域该宠物&#xff0c;未登录会提示登陆然后转到登陆页面 宠物领养页面支持关键字查询符合条件的宠物 当有领养人时就隐藏领养申请按钮 社区交流意见…

嵌入式linux面试题目总结

Linux系统中常见的面试题目&#xff0c;分享&#xff0c;欢迎大家前来交流学习。 1、嵌入式系统中的CAN通信协议是什么&#xff1f; CAN&#xff08;Controller Area Network&#xff09;通信协议是一种广泛应用于嵌入式系统中的串行通信协议。它最初由德国汽车工业联合会开发…

LabVIEW信号时间间隔测量

用LabVIEW软件平台开发一个用于测量两路信号时间间隔的系统。系统利用LabVIEW的数据采集和处理能力&#xff0c;能够准确测量并分析来自不同硬件板卡的信号时间间隔&#xff0c;这对于精确控制和数据分析至关重要。 系统主要由以下几部分组成&#xff1a;数据采集卡、信号处理…

JasperReports渲染报表文件时候,读取图片报错:Byte data not found at:xxx.png.\r\n\tat

目录【知识星球】 1.1、错误描述 1.2、解决方案 1.1、错误描述 最近在工作中&#xff0c;使用JasperReports报表比较多一些&#xff0c;有次线上环境里面运行报错&#xff0c;查看报错日志&#xff0c;如下所示&#xff1a; net.sf.jasperreports.engine.JRException: Byte…

永磁直驱式风力发电虚拟同步机仿真模型Matlab/Simulink模型

很久没有分享虚拟同步机控制相关的方向了&#xff0c;主要是因为硕士之后&#xff0c;也就没再继续深入研究这个课题了&#xff0c;更多的是在电科院的项目里会接触。这个课题方向其实作为硕士毕业课题还是够用的&#xff0c;相对来说也是比较容易毕业的&#xff0c;因为涉及的…

【QT+QGIS跨平台编译】之十三:【giflib+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、giflib介绍二、文件下载三、文件分析四、pro文件五、编译实践一、giflib介绍 GIFlib(又称为Libgif)是一个开源的C语言库,用于处理GIF图像格式。它提供了一组函数和工具,使得开发者可以读取、写入和操作GIF图像文件。 GIFlib支持GIF87a和GIF89a两种版本的GIF格…

代码随想录算法刷题训练营day17

代码随想录算法刷题训练营day17&#xff1a;LeetCode(110)平衡二叉树 LeetCode(110)平衡二叉树 题目 代码 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(…

C++内存管理_C++回顾

内存分布 以下代码&#xff0c;各种变量、常量存储的区域均已标注&#xff1a; 说明&#xff1a; 栈 又叫堆栈——非静态局部变量/函数参数/返回值等等。栈是向下增长的。 内存映射段 是高效的I/O映射方式&#xff0c;用于装载一个共享的动态内存库。用户可使用系统接口创建…

Android 性能优化总结:包体积优化

前言 随着开发不断迭代&#xff0c;App体积越来越大&#xff0c;包大小的增大也会给我们应用带来其他的影响 比如 下载率影响 过大的包体积会影响下载转化率&#xff0c;根据Google Play Store包体积和转化率分析报告显示&#xff0c;平均每增加1M&#xff0c;转化率下降0.2%左…

深入Pyecharts:极坐标系绘制与炫酷效果实战【第39篇—python:极坐标系】

文章目录 深入Pyecharts&#xff1a;极坐标系绘制与炫酷效果实战1. 导入必要的库2. 极坐标系基础3. 定制化极坐标系4. 方向性的极坐标系5. 极坐标系的动画效果6. 自定义极坐标轴标签7. 添加极坐标系的背景图8. 极坐标系的雷达图 总结 深入Pyecharts&#xff1a;极坐标系绘制与炫…

Apache Shiro <= 1.2.4反序列化漏洞攻击 CVE-2016-4437 已亲自复现

Apache Shiro < 1.2.4反序列化漏洞攻击 CVE-2016-4437 已亲自复现 漏洞名称漏洞描述影响版本 漏洞复现环境搭建漏洞利用 修复建议总结 漏洞名称 漏洞描述 在 1.2.5 之前的 Apache Shiro 中&#xff0c;当未为“记住我”功能配置密钥时&#xff0c;远程攻击者可以通过未指定…

docker由浅入深

一、什么是docker docker 顾名思义就是轮船的意思&#xff0c;轮船我们知道是通过集装箱运载货物的东西&#xff0c;那么docker其实也是类似的东西&#xff0c;只是装载的是虚拟的运行程序罢了。其中集装箱在docker里面被称为container&#xff08;后面以容器称之&#xff09;…

怎样自行搭建幻兽帕鲁游戏联机服务器?

幻兽帕鲁是一款深受玩家喜爱的多人在线游戏&#xff0c;为了获取更好的游戏体验&#xff0c;许多玩家希望能够自行搭建幻兽帕鲁游戏联机服务器&#xff0c;本文将指导大家如何自行搭建幻兽帕鲁游戏联机服务器。 自行搭建幻兽帕鲁游戏联机服务器&#xff0c;阿里云是一个不错的选…

2024.1.27 GNSS 学习笔记

1.精确的描述轨道的一组数据(星历)是实现精确定位与导航的基础。 2.GNSS卫星广播星历的提供方式一般有两种&#xff1a;一种是提供开普勒轨道参数和必要的轨道摄动改正项参数&#xff0c;如GPS、BDS、Galileo三大系统采用此种模式&#xff0c;还有QZSS系统&#xff1b;另一种是…

Win32 PE图标资源提取(ICO图标提取)

最近需要写一个提取EXE或者DLL图标资源的功能, 网上找了很久, 要么功能不好用, 最后结果如下: 1.很多是加载为HICON句柄后转换为图片保存, 全损画质..., 2.后来找了个还能用的, 详见 https://github.com/TortoiseGit/TortoiseGit/blob/master/src/Utils/IconExtractor.cpp …

力扣1312. 让字符串成为回文串的最少插入次数

动态规划 思路&#xff1a; 通过插入字符构造回文串&#xff0c;要想插入次数最少&#xff0c;可以将字符串 s 的逆序 s 进行比较找出最长公共子序列&#xff1b;可以先分析&#xff0c;字符串 s 通过插入得到回文串 ps&#xff0c;其中间的字符应该不会变化&#xff1a; 若 s…

计算方法实验1:熟悉MATLAB 环境

一、问题描述 熟悉MATLAB 环境。 二、实验目的 了解Matlab 的主要功能&#xff0c;熟悉Matlab 命令窗口及文件管理&#xff0c;Matlab 帮助系统。掌握命令行的输入及编辑&#xff0c;用户目录及搜索路径的配置。了解Matlab 数据的特点&#xff0c;熟悉Matlab 变量的命名规则&a…