[GPU运算]cuda调用

这段代码是一个标准的CUDA GPU内核函数,用于并行计算向量加法。让我详细解释每一部分的写法和目的:

📌 函数声明部分

c 复制代码
__global__
void vector_add_gpu(float *a, float *b, float *c, int n)

__global__ 关键字的含义

  • 作用:告诉编译器这是一个GPU内核函数,从CPU调用但在GPU上执行
  • 调用方式 :通过 <<<grid, block>>> 语法从CPU调用
  • 特点 :不能有返回值(必须是void),支持异步执行

参数设计要求

  • 指针参数a, b, c 必须是GPU内存地址(设备指针)
  • 值传递n 是普通整数,直接传递给所有线程
  • 限制:不能使用函数指针、不能是类的成员函数、不能有变长参数

🧵 线程索引计算部分

c 复制代码
int index = blockIdx.x * blockDim.x + threadIdx.x;

为什么要这样计算?

CUDA使用**三维网格(Grid)和三维块(Block)**的层次结构:

复制代码
Grid (整个计算空间)
├── Block 0 (包含N个线程)
├── Block 1
└── Block 2
    ├── Thread 0
    ├── Thread 1
    └── Thread 2

计算公式拆解

  • blockIdx.x:当前block在grid中的索引(0,1,2...)
  • blockDim.x:每个block包含的线程数(如1024)
  • threadIdx.x:当前线程在block内的索引(0~1023)

举例:假设每个block有256个线程

  • Block 0的线程:索引0~255
  • Block 1的线程:索引256~511
  • Block 2的线程:索引512~767

🚶 Stride模式部分

c 复制代码
int stride = blockDim.x * gridDim.x;

Stride的含义

stride = 每个block的线程数 × 总block数 = 总共的线程总数

例如

  • 每个block 256线程 × 总共128个block = stride = 32768个线程

为什么要用stride模式?

c 复制代码
for (int i = index; i < n; i += stride) {
    c[i] = a[i] + b[i];
}

这种模式不是 让每个线程只处理一个元素,而是处理多个元素

工作原理

  • 线程0处理:索引 0, stride, 2×stride, 3×stride...
  • 线程1处理:索引 1, stride+1, 2×stride+1...
  • 线程2处理:索引 2, stride+2, 2×stride+2...

优点

  1. 适配任意数组大小:n不一定能被线程总数整除
  2. 负载均衡:所有线程工作量相近
  3. 合并访问:连续的线程访问连续的内存地址,提高内存带宽利用率

📊 实际运行示例

假设配置:<<<2个block, 4个线程/block>>>

复制代码
总线程数 = 2 × 4 = 8个线程
stride = 8

处理数组 n=10个元素:

线程0 (index=0): 处理 [0, 8]
线程1 (index=1): 处理 [1, 9]
线程2 (index=2): 处理 [2]
线程3 (index=3): 处理 [3]
线程4 (index=4): 处理 [4]
线程5 (index=5): 处理 [5]
线程6 (index=6): 处理 [6]
线程7 (index=7): 处理 [7]

⚠️ 写法的关键要求

1. 内存一致性

c 复制代码
// ❌ 错误:不能访问主机内存
__global__ void kernel(int *host_ptr) { 
    int val = host_ptr[0]; // 错误!host_ptr在CPU内存
}

// ✅ 正确:必须使用设备内存
__global__ void kernel(int *dev_ptr) {
    int val = dev_ptr[0]; // 正确
}

2. 线程安全

c 复制代码
// 避免:多个线程写同一位置(竞态条件)
if (threadIdx.x == 0) {
    c[0] = a[0] + b[0];  // 只有一个线程写,安全
}

// 危险:所有线程都写同一位置
c[0] = a[index] + b[index];  // ❌ 数据竞争!

3. 边界检查

c 复制代码
for (int i = index; i < n; i += stride) {  // ✅ 检查边界
    c[i] = a[i] + b[i];
}

如果不检查 i < n,越界访问会导致未定义行为。

🎯 启动配置要求

c 复制代码
int threads_per_block = 256;  // 通常选32的倍数(warp大小)
int blocks_per_grid = (n + threads_per_block - 1) / threads_per_block;

vector_add_gpu<<<blocks_per_grid, threads_per_block>>>(d_a, d_b, d_c, n);

限制

  • 每个block最多1024线程(CUDA限制)
  • block数量可以很大(最多2³¹-1)

💡 性能优化要点

  1. Coalesced Memory Access:stride模式确保相邻线程访问相邻地址
  2. Occupancy:每个SM(流多处理器)的活跃线程数影响性能
  3. 同步 :如果需要在block内同步,使用__syncthreads()

这个设计模式被称为**"grid-stride loop"**,是CUDA编程的最佳实践,兼具灵活性(适配任意数组大小)和性能(合并内存访问)。

相关推荐
AF_INET62 小时前
sensor笔记(一)imx415
c语言·经验分享·音视频·linux驱动·sensor·imx415·datasheet
j7~2 小时前
【C++】C&C++内存管理--之内存分布,operatenew/new,operate/delete的底层原理.
c语言·c++·delete·内存泄漏·new·operate new·动态内存分布
十月的皮皮2 小时前
C语言学习笔记20260603-打印整数(32位)二进制的奇数位和偶数位(2种方法)
c语言·笔记·学习
刃神太酷啦2 小时前
MySQL 库表操作 +数据类型+ 基础概念全梳理----《Hello MySQL!》(2)
java·c语言·数据库·c++·vscode·mysql·adb
caimouse13 小时前
Reactos 第 4 章 对象管理 — 4.5 几个常用的内核函数
c语言·windows·架构
x1387028595714 小时前
c语言排雷游戏(基础版9*9)
c语言·算法·游戏
caimouse14 小时前
Reactos 第 4 章 对象管理 — 4.3 句柄和句柄表(Handle & Handle Table)
c语言·windows·架构
Selina K15 小时前
C中日历时间转换
c语言·开发语言
caimouse16 小时前
Reactos 第 3 章 内存管理 — 【中篇】Hyperspace、系统空间、API 与异常
c语言·开发语言·windows·架构