从零编写三个 CUDA 程序,逐步体验「正确→高效」的优化过程:
约 25 分钟(共 3 个实验,穿插在对应讲解段落后执行)
| 实验 | 对应 PPT 页 | 触发时机 | 时长 |
|---|---|---|---|
| 实验 1: 向量加法 | 第 30–31 页 | Part 3 开始 — 讲完 Host-Device 模型后 | ~8 min |
| 实验 2: Naive 矩阵乘法 | 第 33–34 页 | Part 3 中 — 讲完矩阵乘法映射后 | ~7 min |
| 实验 3: Tiled 矩阵乘法 | 第 36 页 | Part 3 末 — 讲完 Tiling 原理后 | ~10 min |
// vec_add.cu
#include <stdio.h>
#include <cuda_runtime.h>
#include <sys/time.h>
__global__ void vec_add_gpu(float *A, float *B, float *C, int N) {
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < N) {
C[tid] = A[tid] + B[tid];
}
}
double get_time() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec + tv.tv_usec * 1e-6;
}
void vec_add_cpu(float *A, float *B, float *C, int N) {
for (int i = 0; i < N; i++) C[i] = A[i] + B[i];
}
int main() {
int N = 1 << 24; // 16M elements
size_t bytes = N * sizeof(float);
// CPU 版本
float *h_A = (float*)malloc(bytes);
float *h_B = (float*)malloc(bytes);
float *h_C_cpu = (float*)malloc(bytes);
float *h_C_gpu = (float*)malloc(bytes);
for (int i = 0; i < N; i++) {
h_A[i] = rand() / (float)RAND_MAX;
h_B[i] = rand() / (float)RAND_MAX;
}
// === CPU 版本 ===
double start = get_time();
vec_add_cpu(h_A, h_B, h_C_cpu, N);
printf("CPU: %.3f ms\n", (get_time() - start) * 1000);
// === GPU 版本 ===
float *d_A, *d_B, *d_C;
cudaMalloc(&d_A, bytes);
cudaMalloc(&d_B, bytes);
cudaMalloc(&d_C, bytes);
cudaEvent_t gpu_start, gpu_stop;
cudaEventCreate(&gpu_start);
cudaEventCreate(&gpu_stop);
cudaEventRecord(gpu_start);
cudaMemcpy(d_A, h_A, bytes, cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, bytes, cudaMemcpyHostToDevice);
int threads = 256;
int blocks = (N + threads - 1) / threads;
vec_add_gpu<<<blocks, threads>>>(d_A, d_B, d_C, N);
cudaMemcpy(h_C_gpu, d_C, bytes, cudaMemcpyDeviceToHost);
cudaEventRecord(gpu_stop);
cudaEventSynchronize(gpu_stop);
float ms;
cudaEventElapsedTime(&ms, gpu_start, gpu_stop);
printf("GPU (total incl. transfer): %.3f ms\n", ms);
// 验证正确性
int errors = 0;
for (int i = 0; i < N; i++)
if (abs(h_C_cpu[i] - h_C_gpu[i]) > 0.001f) errors++;
printf("Errors: %d / %d\n", errors, N);
cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);
free(h_A); free(h_B); free(h_C_cpu); free(h_C_gpu);
return 0;
}
nvcc -O2 vec_add.cu -o vec_add
./vec_add
修改代码,用三个 cudaEvent 对分别测量 H2D 传输、Kernel 执行、D2H 传输。讨论:
// matmul_naive.cu
#include <stdio.h>
#include <cuda_runtime.h>
#define M 1024
#define N 1024
#define K 1024
#define BLOCK_SIZE 16
__global__ void matmul_naive(float *A, float *B, float *C) {
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
if (row < M && col < N) {
float sum = 0.0f;
for (int k = 0; k < K; k++)
sum += A[row * K + k] * B[k * N + col];
C[row * N + col] = sum;
}
}
int main() {
size_t bytes_A = M * K * sizeof(float);
size_t bytes_B = K * N * sizeof(float);
size_t bytes_C = M * N * sizeof(float);
float *h_A = (float*)malloc(bytes_A);
float *h_B = (float*)malloc(bytes_B);
float *h_C = (float*)malloc(bytes_C);
for (int i = 0; i < M*K; i++) h_A[i] = (float)rand()/RAND_MAX;
for (int i = 0; i < K*N; i++) h_B[i] = (float)rand()/RAND_MAX;
float *d_A, *d_B, *d_C;
cudaMalloc(&d_A, bytes_A);
cudaMalloc(&d_B, bytes_B);
cudaMalloc(&d_C, bytes_C);
cudaMemcpy(d_A, h_A, bytes_A, cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, bytes_B, cudaMemcpyHostToDevice);
dim3 block(BLOCK_SIZE, BLOCK_SIZE);
dim3 grid((N+BLOCK_SIZE-1)/BLOCK_SIZE, (M+BLOCK_SIZE-1)/BLOCK_SIZE);
cudaEvent_t start, stop;
cudaEventCreate(&start); cudaEventCreate(&stop);
cudaEventRecord(start);
matmul_naive<<<grid, block>>>(d_A, d_B, d_C);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
float ms;
cudaEventElapsedTime(&ms, start, stop);
cudaMemcpy(h_C, d_C, bytes_C, cudaMemcpyDeviceToHost);
printf("Naive MatMul (%dx%dx%d): %.3f ms\n", M, N, K, ms);
printf("Arithmetic Intensity: %.1f FLOPs/byte\n", (2.0f*M*N*K)/((M*K+K*N+M*N)*4.0f));
cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);
free(h_A); free(h_B); free(h_C);
return 0;
}
// matmul_tiled.cu
#define TILE_SIZE 16
__global__ void matmul_tiled(float *A, float *B, float *C) {
int row = blockIdx.y * TILE_SIZE + threadIdx.y;
int col = blockIdx.x * TILE_SIZE + threadIdx.x;
__shared__ float As[TILE_SIZE][TILE_SIZE];
__shared__ float Bs[TILE_SIZE][TILE_SIZE];
float sum = 0.0f;
int tiles = (K + TILE_SIZE - 1) / TILE_SIZE;
for (int t = 0; t < tiles; t++) {
// 协作加载 A tile
int a_col = t * TILE_SIZE + threadIdx.x;
As[threadIdx.y][threadIdx.x] = (row < M && a_col < K) ? A[row * K + a_col] : 0.0f;
// 协作加载 B tile
int b_row = t * TILE_SIZE + threadIdx.y;
Bs[threadIdx.y][threadIdx.x] = (b_row < K && col < N) ? B[b_row * N + col] : 0.0f;
__syncthreads();
for (int k = 0; k < TILE_SIZE; k++)
sum += As[threadIdx.y][k] * Bs[k][threadIdx.x];
__syncthreads();
}
if (row < M && col < N)
C[row * N + col] = sum;
}
编译运行三个版本,填写下表:
| 版本 | 时间 (ms) | 加速比 | Global Memory 每个元素被读次数 |
|---|---|---|---|
| CPU 串行 | 1× | — | |
| GPU Naive | K 次 | ||
| GPU Tiled (TILE=16) | K/16 次 | ||
| GPU Tiled (TILE=32) | K/32 次 (注意 bank conflict!) |
__shared__ float As[TILE][TILE+1])for (i=0; i<N; i++) C[i] = A[i] + B[i]; → 按时间顺序,一次一个tid 代替 i → N 个线程同时执行,按空间铺开tid = blockIdx.x * blockDim.x + threadIdx.xrow = blockIdx.y * blockDim.y + threadIdx.y, col = blockIdx.x * blockDim.x + threadIdx.x__syncthreads() 的双重作用TILE+1 paddingfloat4 一次读 4 个元素