Vector Addition

1109 字
6 分钟
Vector Addition

Vector Addition#

基本常识#

CPU = 一个聪明的酋长。脑子快,一次只做一件事。

GPU = 一千个笨原始人。每个都笨,但一千个笨的打赢一个聪明的。

主机 Host = CPU 世界,C++,酋长坐的位置。

设备 Device = GPU 世界,原始人的住处,不同内存,不同空间。

::: tip CPU 不能碰 GPU 内存。GPU 不能碰 CPU 内存,必须过桥传送数据,也就是 PCIe。 :::

三个魔法词#

__global__ void kernel() { }
// CPU 调用, GPU 执行。"原始人,冲!"
__device__ void helper() { }
// GPU 调, GPU 跑。原始人悄悄话。
__host__ void normal() { }
// CPU 调, CPU 跑。普通C++。(默认,可省略)

线程层级:三层分离#

Grid 大营
├── Block 0 小部落
│ ├── Thread 0 原始人
│ ├── Thread 1
│ └── Thread 2 ...
├── Block 1
│ └── Thread 0, 1, 2 ...
└── Block 2 ...

::: tip 每个 block 里,32 个 thread 组成一个 warp。 :::

内置变量:不同原始人有不同的身份划分#

threadIdx.x // 我是部落里第几个原始人? (0 到 blockDim.x-1)
blockIdx.x // 我部落在大营里排第几? (0 到 gridDim.x-1)
blockDim.x // 我部落有多少原始人? (比如 256)
gridDim.x // 大营有多少部落? (比如 4)

执行 <<< >>>:派遣原始人#

kernel<<<部落数, 每部落原始人数>>>(参数);
// 有1000个数。每个原始人处理1个。
// 需要1000个原始人。每部落最多256个。
int N = 1000;
int threads = 256;
int blocks = (N + threads - 1) / threads; // 4 个部落
add_kernel<<<blocks, threads>>>(a, b, c, N);
// 4部落 × 256原始人 = 1024 个原始人。
// 最后一个部落:256 - 24 = 232个干活,24个摸鱼。

内存:四种石头#

内存速度谁能看见生命周期大小
全局慢(像走到山上)所有原始人,所有部落整个程序大(GB)
共享快(像递给旁边兄弟)同部落的原始人一次kernel运行小(48KB)
局部快(口袋里的石头)一个原始人一次kernel运行
寄存器瞬间(手里攥着)一个原始人一次kernel运行最小(每原始人255个)
__global__ void example(float* global_arr) {
// global_arr — 所有人都能看到。远处大石头上写着。
__shared__ float shared_arr[256]; // 只有我部落能看到。篝火旁边。
float local_val = 5.0f; // 我口袋里的。寄存器或局部内存。
}

搬运内存:过桥费#

// 1. 分配 GPU 内存
float* d_data; // "d_" 前缀 = device。原始人约定。
cudaMalloc(&d_data, N * sizeof(float));
// 2. 复制 CPU → GPU(给原始人送活儿)
cudaMemcpy(d_data, h_data, N * sizeof(float), cudaMemcpyHostToDevice);
// 3. 启动 kernel(原始人干活)
kernel<<<blocks, threads>>>(d_data, N);
// 4. 复制 GPU → CPU(原始人带结果回来)
cudaMemcpy(h_result, d_result, N * sizeof(float), cudaMemcpyDeviceToHost);
// 5. 释放 GPU 内存(打扫山洞)
cudaFree(d_data);

::: details 五步法总结

  1. cudaMalloc
  2. cudaMemcpy
  3. kernel <<<>>>
  4. cudaMemcpy
  5. cudaFree :::

__syncthreads() 同步:部落开会#

__global__ void example() {
// 第一阶段:每个原始人摘果子
shared_basket[threadIdx.x] = gather_berry();
__syncthreads(); // 部落里所有原始人在此停下。等每个人。
// 没摘完果子之前,谁也不许偷看篮子。
// 第二阶段:现在安全了,可以数果子
if (threadIdx.x == 0) {
total = sum(shared_basket); // 小队长统计。
}
}

Warp:背后的真相#

::: warning GPU 欺骗了你,它看似只有 256 个原始人一起干活,现实是一个 Warp = 32 个原始人,GPU 一次只执行 32 个。这 32 个必须做同一条指令。(SIMD - 单指令多数据) :::

::: tip Warp 分支发散 = 坏文明 :::

if (threadIdx.x < 16) {
do_A(); // 16 个原始人走 A 路。另外 16 个站着发呆。
} else {
do_B(); // 16 个原始人走 B 路。另外 16 个站着发呆。
}
// 总时间 = A时间 + B时间。一半原始人永远在摸鱼。浪费!

::: tip 好代码避免 warp 里写 if-else。32 个原始人一起齐步走。 :::

第一个程序:Vector Addition#

题目#

代码#

#include <cuda_runtime.h>
__global__ void vector_add(const float* A, const float* B, float* C, int N) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < N) {
C[i] = A[i] + B[i];
}
}
// A, B, C are device pointers (i.e. pointers to memory on the GPU)
extern "C" void solve(const float* A, const float* B, float* C, int N) {
int threadsPerBlock = 256;
int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
vector_add<<<blocksPerGrid, threadsPerBlock>>>(A, B, C, N);
cudaDeviceSynchronize();
}

代码速查表:军师级别的原始人专用#

干啥咋写
GPU 函数__global__ void name(...)
启动name<<<部落数, 原始人数>>>(参数)
我的编号blockIdx.x * blockDim.x + threadIdx.x
部落墙__shared__ float arr[大小]
部落等齐__syncthreads()
GPU 分配cudaMalloc(&ptr, 字节数)
拷内存cudaMemcpy(目标, 源, 字节, 方向)
等 GPU 干完cudaDeviceSynchronize()
GPU 释放cudaFree(ptr)
Vector Addition
https://dongyanzhang.com/posts/leetgpu/vector-addition/
作者
阿东阿言
发布于
2026-07-12
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
阿东阿言
I share my notes | projects | experience here...
公告
欢迎来到阿东阿言的博客
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
分类
标签
站点统计
文章
25
分类
3
标签
5
总字数
11,186
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
v1.0.0
文章许可
CC BY-NC-SA 4.0