CUDA基础知识巩固检验练习题【附有参考答案】(5)

以下是针对 2.2 Writing CUDA SIMT Kernels2.2.1-2.2.2 子章节内容的详细知识点整理及配套练习题。


2.2. Writing CUDA SIMT Kernels (编写CUDA SIMT内核)

核心思想

  • CUDA C++内核的编写方式与传统CPU代码类似
  • 但GPU有独特特性可用于提升性能
  • 理解线程调度、内存访问和执行流程有助于最大化资源利用率

2.2.1. Basics of SIMT (SIMT基础)

SIMT模型概述

概念 说明
CUDA线程 从开发者视角看,是并行性的基本单位
Warp(线程束) GPU执行的基本单位,32个线程为一组
SIMT 单指令多线程(Single Instruction, Multiple Threads)模型

SIMT模型的关键特性

线程独立性
  • 每个线程维护自己的状态(程序计数器、寄存器等)
  • 每个线程可以有自己的控制流
  • 从功能角度看,每个线程可以执行不同的代码路径
性能优化要点
  • 避免线程束发散:同一warp内的线程应尽量减少执行不同代码路径的情况
  • 线程束发散会导致串行化执行,降低并行效率
SIMT vs SIMD
特性 SIMT (CUDA) SIMD (CPU向量化)
执行模型 多线程并行执行同一条指令 单指令操作多个数据元素
线程独立性 每个线程有独立状态和控制流 无独立线程概念
编程模型 标量编程,类似多线程 向量编程,需显式使用向量指令

2.2.2. Thread Hierarchy (线程层次结构)

层次结构概述

复制代码
Grid (网格)
    ├── Thread Block 0 (线程块0)
    │       ├── Thread 0
    │       ├── Thread 1
    │       └── ...
    ├── Thread Block 1
    │       ├── Thread 0
    │       ├── Thread 1
    │       └── ...
    └── ...

维度支持

  • 网格:可以是1维、2维或3维
  • 线程块:可以是1维、2维或3维

内建变量汇总

变量 类型 描述 设置时机
gridDim.x / .y / .z dim3 网格在x、y、z维度的大小 内核启动时设置
blockDim.x / .y / .z dim3 线程块在x、y、z维度的大小 内核启动时设置
blockIdx.x / .y / .z uint3 当前线程块在网格中的索引 执行时变化
threadIdx.x / .y / .z uint3 当前线程在线程块中的索引 执行时变化

多维索引的使用目的

多维线程块和网格只是为了方便,不影响性能

  • 提供编程便利性,便于映射到问题域(如矩阵、图像、体数据)
  • 线程块内的线程有可预测的线性化顺序

线程线性化顺序

存储顺序(C语言行优先)
  • x维度变化最快(最内层)
  • y维度次之(步长为 blockDim.x
  • z维度最慢(步长为 blockDim.x * blockDim.y
线性索引计算公式

1维线程块(最常用):

cpp 复制代码
int tid = threadIdx.x + blockIdx.x * blockDim.x;

2维线程块

cpp 复制代码
int tid_x = threadIdx.x + blockIdx.x * blockDim.x;
int tid_y = threadIdx.y + blockIdx.y * blockDim.y;
int linear_index = tid_y * gridDim.x * blockDim.x + tid_x;

3维线程块

cpp 复制代码
int tid_x = threadIdx.x + blockIdx.x * blockDim.x;
int tid_y = threadIdx.y + blockIdx.y * blockDim.y;
int tid_z = threadIdx.z + blockIdx.z * blockDim.z;
int linear_index = tid_z * gridDim.x * blockDim.x * gridDim.y * blockDim.y 
                   + tid_y * gridDim.x * blockDim.x + tid_x;

线程到Warp的映射

  • 线性化顺序影响线程如何被分配到warp中
  • 连续线程索引 (threadIdx.x连续)会被分配到同一个warp
  • 这很重要,因为:
    • 同一warp内的线程执行同一条指令
    • 连续线程通常访问连续内存,有利于内存合并访问

图9示例说明

图9展示了一个简单的2D网格,包含1D线程块的示例:

  • 网格是2维的(有行和列)
  • 每个线程块是1维的(只有x维度)
  • 这种配置常用于处理2D数据(如矩阵),但每个块处理一维数据段

编程模式总结

计算全局唯一索引的模式

1D网格 + 1D块(最常见):

cpp 复制代码
int global_idx = blockIdx.x * blockDim.x + threadIdx.x;

1D网格 + 2D块

cpp 复制代码
int global_idx = blockIdx.x * blockDim.x * blockDim.y 
                 + threadIdx.y * blockDim.x + threadIdx.x;

2D网格 + 2D块(适用于2D数据):

cpp 复制代码
int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;
int global_idx = y * width + x;  // width是全局宽度

最佳实践总结

  1. 避免线程束发散:尽量让同一warp内的线程执行相同代码路径
  2. 合理使用多维索引:根据问题域选择合适维度,便于代码理解
  3. 利用线性化顺序:确保连续线程访问连续内存,实现合并访问
  4. 计算全局索引:使用内建变量组合计算每个线程的全局唯一ID
  5. 注意边界条件:处理数据大小不是块大小整数倍的情况
  6. 理解warp分配:threadIdx.x连续的线程在同一warp,影响内存访问模式

知识点巩固练习题

一、选择题

1. 关于SIMT模型,下列说法正确的是?

A. SIMT模型中所有线程必须执行完全相同的代码路径

B. SIMT模型中每个线程可以有自己的控制流,但线程束发散会影响性能

C. SIMT模型不允许线程有不同的状态

D. SIMT就是SIMD的另一种说法

2. 在CUDA中,线程束(Warp)的大小是多少?

A. 16个线程

B. 32个线程

C. 64个线程

D. 取决于GPU型号

3. 关于多维线程块和网格,下列说法正确的是?

A. 使用2D线程块比1D线程块性能更好

B. 多维只是为了编程方便,不影响性能

C. 必须使用与问题维度相同的维度

D. 3D网格比2D网格性能更好

4. 在线程块的线性化顺序中,哪个维度变化最快?

A. x维度

B. y维度

C. z维度

D. 取决于编译器

5. 如果一个线程块的大小是blockDim.x=16, blockDim.y=8,那么threadIdx.y的步长是多少?

A. 8

B. 16

C. 128

D. 1

6. 哪个内建变量用于获取线程块在网格中的索引?

A. threadIdx

B. blockIdx

C. blockDim

D. gridDim

7. 在1D网格和1D线程块的配置下,计算全局唯一索引的正确公式是?

A. threadIdx.x

B. blockIdx.x

C. threadIdx.x + blockIdx.x

D. blockIdx.x * blockDim.x + threadIdx.x

8. 关于线程束发散,下列说法正确的是?

A. 线程束发散可以提高并行性

B. 线程束发散会导致同一warp内的线程串行执行不同分支

C. 线程束发散无法避免

D. 线程束发散只影响内存访问

9. 如果一个线程块是2D的,blockDim.x=32, blockDim.y=4,那么一个warp(32线程)包含哪些线程?

A. 32个连续的threadIdx.x

B. 32个连续的threadIdx.y

C. 4行完整的x维度线程(每行32个)

D. 无法确定

10. 以下哪个场景最容易导致线程束发散?

A. 所有线程执行相同的指令

B. 根据threadIdx.x的值执行不同的分支

C. 所有线程访问连续的内存地址

D. 所有线程计算相同的表达式

二、填空题

**1. CUDA线程是并行性的 _____ 单位,而GPU执行的基本单位是 _____

**2. SIMT模型允许每个线程维护自己的 __________

**3. 为了提高性能,应尽量减少同一 _____ 内的线程执行不同的代码路径。

**4. 线程层次结构中,多个线程组成 _____ ,多个 _____ 组成网格。

5. 网格和线程块都可以是 _____ ** 或 **** 维的。

6. 内建变量 _____ 用于获取线程块的大小,_____** 用于获取网格的大小。

7. 在C语言行优先存储中,线性化顺序是 _____ 维度变化最快, ** 维度次之,**** 维度最慢。

**8. 如果blockDim.x=16, blockDim.y=8, blockDim.z=4,那么threadIdx.z的步长是 _____

**9. 连续线程索引(threadIdx.x连续)会被分配到同一个 _____ 中。

**10. 多维线程块和网格主要是为了 _____ 方便,不影响性能。

三、简答题

1. 解释SIMT模型的基本概念及其与SIMD的主要区别。

2. 什么是线程束发散?为什么应该避免它?

3. 描述CUDA的线程层次结构,并说明各层次的作用。

4. 列出所有与线程层次相关的CUDA内建变量,并说明每个变量的含义。

5. 解释为什么threadIdx.x连续的线程会被分配到同一个warp,这对性能有什么影响?

6. 给定一个2D网格(gridDim.x=4, gridDim.y=3)和2D线程块(blockDim.x=16, blockDim.y=8),写出计算全局唯一线程索引的公式。

7. 为什么说多维线程块和网格只是编程便利,不影响性能?

四、计算题

1. 给定以下内核启动配置:

cpp 复制代码
dim3 grid(4, 2);
dim3 block(16, 8);
kernel<<<grid, block>>>();

计算:

  • 总线程块数量
  • 总线程数量
  • 每个线程块内的线程数量
  • 线程块(1,1)中线程(5,3)的线性化索引(块内)
  • 整个网格中,线程块(2,0)中线程(10,4)的全局唯一线程索引(假设所有索引从0开始,按x优先线性化)

2. 编写一个内核函数,使用2D线程块处理2D矩阵,每个线程处理一个矩阵元素。要求:

  • 矩阵大小为1024×1024
  • 选择合适的网格和块维度
  • 计算每个线程负责的全局行和列索引
  • 添加边界检查

3. 分析以下代码,指出其中可能存在的性能问题:

cpp 复制代码
__global__ void divergentKernel(float* data, int N) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    
    if (idx < N) {
        if (idx % 2 == 0) {
            data[idx] = idx * 2.0f;
        } else {
            data[idx] = idx * 3.0f;
        }
    }
}

参考答案

一、选择题答案

  1. B(SIMT允许独立控制流,但发散影响性能)
  2. B(warp大小固定为32线程)
  3. B(多维只是为了编程方便)
  4. A(x维度变化最快)
  5. B(threadIdx.y的步长是blockDim.x=16)
  6. B(blockIdx是块索引)
  7. D(经典公式:blockIdx.x * blockDim.x + threadIdx.x)
  8. B(发散导致串行执行不同分支)
  9. A(一个warp包含32个连续的threadIdx.x)
  10. B(根据threadIdx分支容易导致发散)

二、填空题答案

  1. 基本,线程束(warp)
  2. 状态,控制流
  3. 线程束(warp)
  4. 线程块,线程块
  5. 1维,2维,3维
  6. blockDimgridDim
  7. x,y,z
  8. blockDim.x * blockDim.y = 16 * 8 = 128
  9. 线程束(warp)
  10. 编程

三、简答题答案要点

1. SIMT vs SIMD

  • SIMT:单指令多线程,每个线程有独立状态和控制流,标量编程模型
  • SIMD:单指令多数据,无独立线程概念,需显式向量化编程
  • 主要区别:SIMT提供线程抽象,编程更接近多线程模型

2. 线程束发散

  • 定义:同一warp内的线程因条件分支执行不同代码路径
  • 影响:导致不同路径串行执行,降低并行效率
  • 避免方法:重新组织数据或算法,使同一warp内线程行为一致

3. 线程层次结构

  • 线程:最小执行单元,每个线程执行内核代码
  • 线程块:线程集合,共享内存和同步,驻留同一SM
  • 网格:线程块集合,覆盖整个问题域
  • 作用:提供多层次并行,支持协作和资源共享

4. 内建变量

  • gridDim:网格维度(多少块)
  • blockDim:块维度(每块多少线程)
  • blockIdx:当前块索引
  • threadIdx:当前线程在块内的索引

5. 连续threadIdx.x在同一warp的影响

  • 原因:线程按x优先线性化分配到warp
  • 影响:有利于内存合并访问,连续线程访问连续地址
  • 优化:设计算法时利用这一特性

6. 2D全局索引公式

cpp 复制代码
int blockId = blockIdx.y * gridDim.x + blockIdx.x;
int threadIdInBlock = threadIdx.y * blockDim.x + threadIdx.x;
int globalIdx = blockId * (blockDim.x * blockDim.y) + threadIdInBlock;

或者按行列:

cpp 复制代码
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
int globalIdx = row * (gridDim.x * blockDim.x) + col;

7. 多维仅为编程便利的原因

  • 硬件层面,所有线程最终线性化执行
  • 多维映射便于问题域表达(矩阵、图像等)
  • 性能取决于线程组织和内存访问模式,而非维度数量

四、计算题答案

1. 计算题答案

  • 总线程块数量:4 × 2 = 8
  • 总线程数量:8 × (16 × 8) = 8 × 128 = 1024
  • 每块线程数:16 × 8 = 128
  • 块(1,1)中线程(5,3)的块内线性索引:threadIdx.y * blockDim.x + threadIdx.x = 3 × 16 + 5 = 53
  • 块(2,0)中线程(10,4)的全局索引:
    • 块ID = blockIdx.y * gridDim.x + blockIdx.x = 0 × 4 + 2 = 2
    • 块内线程ID = 4 × 16 + 10 = 74
    • 每块线程数 = 128
    • 全局ID = 2 × 128 + 74 = 330

2. 2D矩阵处理内核

cpp 复制代码
__global__ void matrixProcess(float* matrix, int width, int height) {
    // 计算全局行列
    int col = blockIdx.x * blockDim.x + threadIdx.x;
    int row = blockIdx.y * blockDim.y + threadIdx.y;
    
    // 边界检查
    if (row < height && col < width) {
        int idx = row * width + col;
        matrix[idx] = matrix[idx] * 2.0f;  // 示例操作
    }
}

// 启动配置
dim3 block(16, 16);  // 每块16x16线程
dim3 grid((1024 + 15) / 16, (1024 + 15) / 16);  // 64x64块
matrixProcess<<<grid, block>>>(d_matrix, 1024, 1024);

3. 性能问题分析

  • 问题:根据idx % 2进行分支,会导致warp发散

  • 由于idx连续,每个warp中一半线程取模2为0,一半为1

  • 改进方法:

    cpp 复制代码
    // 方法1:重新组织数据,使连续线程处理相同类型
    // 方法2:使用三元运算符(可能被优化)
    // 方法3:使用算术方法统一计算
    data[idx] = idx * (2.0f + (idx % 2));
相关推荐
70asunflower1 小时前
CUDA基础知识巩固检验练习题【附有参考答案】(6)
c++·人工智能·cuda
波动几何1 小时前
人工智能编程之复杂功能描述样本(待办任务)
人工智能
Flying pigs~~1 小时前
机器学习之数据挖掘时间序列预测
人工智能·算法·机器学习·数据挖掘·线性回归
东荷新绿1 小时前
【论文学习】ESEFR-GAN:一种不依赖先验信息的人脸复原框架
人工智能·生成对抗网络·人脸复原·eaai
Lim小刘1 小时前
【保姆级教程】在 AWS Lightsail 上快速部署 OpenClaw:开启您的个人 AI 助手
人工智能·云计算·aws
刘 大 望1 小时前
使用AI IDE从0到1开发五子棋对战项目(vibe coding)
java·人工智能·spring boot·redis·ai·java-rabbitmq·ai编程
液态不合群1 小时前
AI赋能下的中国低代码市场:从工具革新到产业数字化核心引擎
java·人工智能·低代码·架构
shuidaoyuxing1 小时前
在汽车领域,“辅助驾驶”与“自动驾驶”的区分及标准的讲解及介绍
人工智能·自动驾驶·汽车
李昊哲小课2 小时前
Python OS模块详细教程
服务器·人工智能·python·microsoft·机器学习