3. cuda执行模型
本章主要介绍的是gpu的架构和如何配置达到最高运算的,增加其吞吐量(gpu的并行性)。
3.1 cuda执行模型概述
3.1.1 gpu架构描述
RTX 对应的架构是 Ampere SM(流式多处理器)
组件名称 | 功能描述 |
---|---|
INT32 核心 | 用于整数运算,支持并发执行(与 FP32 并行),适合逻辑、索引类任务。 |
FP32 核心(CUDA Cores) | 用于执行单精度浮点运算(如加减乘除等),Ampere 每个 SM 有 128 个 FP32 核心,吞吐量翻倍。 |
Tensor Cores(张量核心) | 第三代 Tensor Core,支持 FP16、BF16、TF32、INT8 等多种数据类型,广泛用于深度学习中的矩阵乘法(如 GEMM)。 |
LD/ST 单元(Load/Store Units) | 负责全局内存与 SM 寄存器/共享内存之间的数据传输。 |
共享内存 / L1 Cache(可配置) | 每个 SM 有高达 164 KB 的共享内存 + L1 Cache,程序可配置(如 64 KB L1 + 100 KB shared)。共享内存加快线程间通信。 |
寄存器文件(Register File) | 用于存储每个线程的局部变量,每个 SM 共有 65536 个 32-bit 寄存器。 |
调度器(Warp Scheduler) | 每个 SM 有 4 个 warp 调度器,可并发调度 4 个 warp,提升并发性和执行效率。 |
SFU(特殊功能单元) | 执行特殊数学函数,如三角函数、开根号、指数等。 |
纹理单元 / 数据缓存单元(Texture Units) | 加速图像、纹理等访问操作,也可以用于普通内存数据采样。 |
项目 | 数量 |
---|---|
CUDA Cores(FP32) | 128 个(每 SM) |
Tensor Cores | 4 个(每 SM) |
Warp 调度器-->线程束 | 4 个 |
共享内存 | 最多 164 KB(与 L1 Cache 可配置共享) |
最大线程数(每 SM) | 2048 |
最大 Warps(每 SM) | 64 |
最大寄存器数量(32-bit) | 65536 |
关系:一个线程块只能在一个SM上被调度。一旦这个线程块在SM上被调度的话,就会保存在改SM上直至执行完成。
注:wrap 是32 个线程。这是固定的。(固定的,这是硬件执行单元的基本单位)
c
#include <stdio.h>
#include <cuda_runtime.h>
int main() {
int dev = 0;
cudaDeviceProp prop;
cudaGetDeviceProperties(&prop, dev);
printf("Device %d: %s\n", dev, prop.name);//gpu的型号
printf(" CUDA Capability: %d.%d\n", prop.major, prop.minor);//计算能力(Compute Capability)是 8.6
printf(" Total Global Memory: %.2f GB\n", prop.totalGlobalMem / (1024.0 * 1024 * 1024));//显存大小(可用的全局内存),用于存储数据、模型、权重等。
printf(" Shared Memory Per Block: %zu bytes\n", prop.sharedMemPerBlock);//每个线程块(block)可用的共享内存大小,共享内存是 Block 内线程共享的高速内存,类似"线程局部缓存"。
printf(" Registers Per Block: %d\n", prop.regsPerBlock);//每个线程块最多可使用的寄存器数量。寄存器是线程最快的本地内存资源。
printf(" Warp Size: %d\n", prop.warpSize);//一个 warp 有 32 个线程。warp 是 GPU 执行的最小调度单位。
printf(" Max Threads Per Block: %d\n", prop.maxThreadsPerBlock);//一个线程块中最多可以容纳线程数量
printf(" Max Threads Per Multiprocessor: %d\n", prop.maxThreadsPerMultiProcessor);//一个 SM(流式多处理器)最多可以同时调度的线程数量
printf(" Multiprocessor Count: %d\n", prop.multiProcessorCount);//表示该 GPU 拥有多少个个 SM,即 82 个并行计算单元。
printf(" Max Threads Dimensions (block): (%d, %d, %d)\n",
prop.maxThreadsDim[0], prop.maxThreadsDim[1], prop.maxThreadsDim[2]);//线程块(block)的最大形状,表示你可以定义一个 dim3 block(1024, 1, 1) 或 dim3 block(32, 32, 1) 这样的块
printf(" Max Grid Size: (%d, %d, %d)\n",
prop.maxGridSize[0], prop.maxGridSize[1], prop.maxGridSize[2]);//网格(grid)维度的最大值,用于定义 block 的布局,通常非常大。
printf(" Clock Rate: %.2f MHz\n", prop.clockRate / 1000.0);//核心频率(SM 的时钟频率),单位是 MHz,影响执行速度。
printf(" Memory Clock Rate: %.2f MHz\n", prop.memoryClockRate / 1000.0);//显存的运行频率,决定显存带宽。
printf(" Memory Bus Width: %d bits\n", prop.memoryBusWidth);//显存总线宽度,和 memory clock 一起决定显存带宽。
printf(" L2 Cache Size: %d bytes\n", prop.l2CacheSize);//L2 缓存大小,所有 SM 共用。
printf(" Max Threads per SM: %d\n", prop.maxThreadsPerMultiProcessor);// 每个 SM(Streaming Multiprocessor)中可同时活跃线程的最大数量
printf(" Concurrent Kernels: %s\n", prop.concurrentKernels ? "Yes" : "No");//是否支持多个 kernel 并发执行(支持更复杂的并行调度)。
printf(" Compute Mode: %d\n", prop.computeMode);//设备工作模式,常见值有:0: 默认,可被多个进程访问。1: 只允许一个进程访问(独占)。2: 只允许主机访问(禁用 GPU 计算)。
printf(" ECC Enabled: %s\n", prop.ECCEnabled ? "Yes" : "No");//是否启用 ECC(错误检测纠正),开启时数据更可靠但性能略下降。
return 0;
}
Device 0: GeForce RTX 3090
CUDA Capability: 8.6
Total Global Memory: 23.70 GB
Shared Memory Per Block: 49152 bytes
Registers Per Block: 65536
Warp Size: 32
Max Threads Per Block: 1024
Max Threads Per Multiprocessor: 1536
Multiprocessor Count: 82
Max Threads Dimensions (block): (1024, 1024, 64)
Max Grid Size: (2147483647, 65535, 65535)
Clock Rate: 1695.00 MHz
Memory Clock Rate: 9751.00 MHz
Memory Bus Width: 384 bits
L2 Cache Size: 6291456 bytes
Max Threads per SM: 1536
Concurrent Kernels: Yes
Compute Mode: 0
ECC Enabled: No
3.2 理解线程束执行的本质
Warp 是 CUDA 中执行的最小调度单元。
- 一个 Warp 包含 32 个线程(这是固定的,NVIDIA 定义的标准)。
- 当你在 CUDA 中启动一个线程块(block)时,线程会被自动划分成若干个 warp。
- GPU 中的 SM(流处理器)以 warp 为单位进行调度与执行。
3.2.1 线程束和线程块
线程束是SM中基本的执行单位 。当一个线程块的网格被启动时,网格中的线程块分布在SM中。一旦线程块被调度在SM上,线程块中的线程会被进一步划分为线程束。一个线程束由32个线程组成,在一个线程束中,所有的线程将按照单指令多线程(SIMT)方式执行。
线程块:
逻辑角度看:线程块是线程的集合,它们可以被组织为一维、二维、三维布局。
硬件角度看:线程块是一维线程束的集合。在线程块中线程被组成一维布局,每32个连续的线程组成一个线程束。
3.2.2 线程束的分化。
概念:在同一个线程束里面执行不同的指令,称作是线程束的分化。
eg:
if(cond) {
} else {
}
为了获取最佳性能,应该避免同一个线程束中有不同的执行路径。
c
#include<cuda_runtime.h>
#include<stdio.h>
#include<sys/time.h>
double cpuSecond() {
struct timeval tp;
gettimeofday(&tp,NULL);
return ((double)tp.tv_sec + (double)tp.tv_usec*1e-6);
}
//线程束分化
// warp 中 32 个线程中,有一半是奇数 tid,一半是偶数 tid;
// 所以同一个 warp 中一半线程执行 a=100,另一半执行 b=200;
// 会产生 warp 分歧,GPU 内部必须分别串行执行两个路径。
__global__ void mathKernel1(float* c) {
int tid = blockIdx.x * blockDim.x + threadIdx.x;
float a,b;
a = b = 0.0f;
if(tid % 2 == 0) {
a = 100.0f;
} else {
b = 200.0f;
}
c[tid] = a + b;
}
//线程束方法
// (tid / warpSize) 代表当前线程属于哪一个 warp;
// 所以同一个 warp 里的线程 (tid / warpSize) % 2 是相同的;
// 所以 同一个 warp 全部执行 a=100 或全部执行 b=200;
// 无 warp 分歧,效率高,GPU 可最大化并行执行。
__global__ void mathKernel2(float* c) {
int tid = blockIdx.x * blockDim.x + threadIdx.x;
float a,b;
a = b = 0.0f;
if((tid / warpSize) % 2 == 0) {
a = 100.0f;
} else {
b = 200.0f;
}
c[tid] = a + b;
}
__global__ void warmup_kernel() {
// 空操作,或简单加法
}
int main(int argc,char** argv) {
int dev = 0;
cudaDeviceProp deviceprop;
int size = 64;
int blocksize = 64;
cudaGetDeviceProperties(&deviceprop , dev);
if(argc > 1) {
blocksize = atoi(argv[1]);
}
if(argc > 2) {
size = atoi(argv[2]);
}
dim3 block(blocksize,1);
dim3 grid((size + block.x -1)/block.x,1);
printf("block.x %d \t grid.x : %d\n",block.x,grid.x);
float* d_c;
size_t nBytes = size * sizeof(float);
cudaMalloc( (float**)&d_c,nBytes);
double isStart,isEnd;
cudaDeviceSynchronize();
isStart = cpuSecond();
warmup_kernel<<<grid,block>>> ();
cudaDeviceSynchronize();
isEnd = cpuSecond();
printf("warmup <<<%4d,%4d>>> cost %f sec\n",grid.x,block.x,isEnd - isStart);
cudaDeviceSynchronize();
isStart = cpuSecond();
mathKernel1<<<grid,block>>> (d_c);
cudaDeviceSynchronize();
isEnd = cpuSecond();
printf("mathKernel1 <<<%4d,%4d>>> cost %f sec\n",grid.x,block.x,isEnd - isStart);
isStart = cpuSecond();
mathKernel2<<<grid,block>>> (d_c);
cudaDeviceSynchronize();
isEnd = cpuSecond();
printf("mathKernel2 <<<%4d,%4d>>> cost %f sec\n",grid.x,block.x,isEnd - isStart);
cudaFree(d_c);
cudaDeviceReset();
return 0;
// block.x 64 grid.x : 1
// warmup <<< 1, 64>>> cost 0.000145 sec
// mathKernel1 <<< 1, 64>>> cost 0.000171 sec
// mathKernel2 <<< 1, 64>>> cost 0.000009 sec
}
3.2.3 资源分配
线程束的上下文主要由以下资源组成:
程序计数器:程序计数器是一个寄存器,用于记录当前要执行的指令地址 ;在 CUDA 中,一个 warp 共用一个 PC ,因为一个 warp 内的线程是SIMD 执行(同一指令)
寄存器:寄存器是每个线程私有的高速内存空间 ,用于保存局部变量、中间计算结果等;每个线程都有自己独立的一组寄存器,warp 中不同线程不共享寄存器。
共享内存:是线程块(block)内所有线程共享的内存区域;用于线程间通信和数据共享;在硬件上位于 SM(Streaming Multiprocessor)内部,访问速度比全局内存快得多。
当计算资源(如寄存器和共享内存) 已分配给线程块时。线程块被称为活跃的快。它所包含的线程束被称为活跃的线程束。活跃的线程束可以进一步被分为以下三种类型:
选定线程束
阻塞的线程束
符合条件的线程束
如何判断线程束是否满足执行条件?
- 32个CUDA核心均可用于执行。
- 当前指令中所有的参数都已经就绪。
3.2.4 延迟隐藏
指令延迟可分为以下两种类型:
- 算数指令延迟:一个算数操作从开始到它产生输出之间的时间。10~20个周期
- 内存指令延迟:发送出的加载或内存操作和数据到达目的地之间的时间。400~800个周期
利特尔法则:
所需线程束的数量 = 延迟 * 吞吐量
假设你要保持每个周期执行6个线程束的吞吐量,延迟是5个周期的话,则至少需要30个线程束
带宽与吞吐量的概念:
带宽:理论峰值
吞吐量:已达到的值。
如何提高并行:
- 指令级并行:一个线程中有许多独立的指令。在一个线程内部 ,尽量安排多个相互独立、不依赖的指令,让 GPU 在执行时可以同时处理它们。
c
float a = x + y; // 指令 1
float b = m * n; // 指令 2(和上面的无依赖)
float c = a + b; // 指令 3(依赖 a 和 b)
- 线程级并行:很多并发地符号条件的线程。让尽可能多的线程同时运行,通过 CUDA 的调度器隐藏延迟、提高 GPU 利用率。
c
__global__ void addKernel(float* a, float* b, float* c) {
int i = threadIdx.x + blockDim.x * blockIdx.x;
c[i] = a[i] + b[i];
}
查看gpu的内存吞吐量:
nvidia-smi -q | grep -A 5 "Max Clocks"
RTX 3090 有 82 个 SM,所以至少需要:
32 warps × 82 SM = 2624 个线程束(活跃 warp)
换算线程数:
2624 warps × 32 线程 = 83,968 个线程
3.2.5 占用率
占用率 = 活跃的线程束的数量/ 最大的线程束的数量
c
#include<stdio.h>
#include<cuda_runtime.h>
int main() {
int dev = 0;
cudaDeviceProp iprop;
cudaGetDeviceProperties(&iprop ,dev );
printf("number of multiprocessors %d\n",iprop.multiProcessorCount);//含义:GPU 上的多处理器(SM,Streaming Multiprocessor)数量。
//作用:每个 SM 可以并行处理多个线程块,是 GPU 并行处理能力的重要指标。
printf("const memeory :%4.2f kb\n",iprop.totalConstMem / 1024.0);
//含义:设备上常量内存(constant memory)的总大小(以字节为单位)。
//作用:常量内存是一个只读的内存区域,适用于线程之间共享的常量数据,访问速度比全局内存快。
printf("shared memory per block %4.2fkb\n",iprop.sharedMemPerBlock / 1024.0);
printf("total of registers available per bolck %d\n",iprop.regsPerBlock);
//含义:每个线程块可用的寄存器数量。作用:每个线程在执行时会使用寄存器,寄存器越多,线程执行速度越快;但数量有限,会影响线程并发度。
printf("Warp size :%d\n",iprop.warpSize);//一个 warp 中包含的线程数量。
printf("max number of per block :%d\n",iprop.maxThreadsPerBlock);//:每个线程块可容纳的最大线程数。
printf("max number of threads of per multiprocessor :%d\n",iprop.maxThreadsPerMultiProcessor);//每个 SM(多处理器)可同时执行的最大线程数量。
printf("max number of warps of per multiprocessor : %d\n",iprop.maxThreadsPerMultiProcessor / 32);//每个多处理器上最大可支持的 warp 数量。
return 0;
}
网格和线程块大小准则:
1. 保证每个块中的线程数量是线程束大小(32)的倍数。
2. 避免块太小,每个块至少要有128 或者256个线程。
3. 根据内核资源的需求调整块的大小。
4. 块的数量要远远大于SM的数量,从而在设备中可以显示足够多的并行。
c
#include <stdio.h>
#include <cuda_runtime.h>
// 核函数
__global__ void vectorAdd(const float* A, const float* B, float* C, int N) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < N) {
C[idx] = A[idx] + B[idx];
}
}
int main() {
const int N = 1 << 20; // 1M
size_t size = N * sizeof(float);
float *h_A = (float*)malloc(size);
float *h_B = (float*)malloc(size);
float *h_C = (float*)malloc(size);
// 初始化
for (int i = 0; i < N; ++i) {
h_A[i] = i;
h_B[i] = N - i;
}
// 设备内存
float *d_A, *d_B, *d_C;
cudaMalloc(&d_A, size);
cudaMalloc(&d_B, size);
cudaMalloc(&d_C, size);
cudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice);
// 使用 dim3 设置 block 和 grid
dim3 threadsPerBlock(32 * 8); // 256 threads, 是 warp 大小的倍数
dim3 blocksPerGrid((N + threadsPerBlock.x - 1) / threadsPerBlock.x); // 计算需要的 blocks 数
printf("Using dim3: %d blocks × %d threads\n", blocksPerGrid.x, threadsPerBlock.x);
// 调用核函数
vectorAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, N);
cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost);
// 验证
for (int i = 0; i < 10; i++) {
printf("C[%d] = %f\n", i, h_C[i]);
}
// 清理
cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C);
free(h_A);
free(h_B);
free(h_C);
return 0;
}
3.2.6 同步
栅栏同步是一个原语,它在诸多编程语言中都很常见。在cuda中,同步可以在两个级别执行:
系统级:等待主机和设备完成所有工作。cudaDeviceSynchronize(void)
块级:在设备执行过程中等待一个线程块中所有线程达到同一点。_syncthreads(void) 这个函数启动时,在同一个线程块的每个线程都必须等待所有线程执行完成后才可返回。
3.3 并行性的表现
c
#include<stdio.h>
#include<cuda_runtime.h>
#include<sys/time.h>
#define CHECK(call) \
do { \
const cudaError_t error = call; \
if (error != cudaSuccess) { \
printf("CUDA Error: %s:%d, ", __FILE__, __LINE__); \
printf("code: %d, reason: %s\n", error, cudaGetErrorString(error)); \
exit(1); \
} \
} while (0)
void init_data(float *p,const int size) {
for(int i = 0;i < size; ++i) {
p[i] = i;
}
}
void print_check(float *c ,const int nx, const int ny) {
float* ic = c;
printf("nx : %d \t ny :%d \n",nx,ny);
for(int i = 0 ;i < ny ; ++i) {
for(int y = 0; y < nx; ++y) {
printf("%f\t",ic[y]);
}
ic += nx;
printf("\n");
}
printf("\n");
}
__global__ void printThreadIndex(float *a,const int nx, const int ny) {
int ix = blockIdx.x * blockDim.x + threadIdx.x;
int iy = blockIdx.y * blockDim.y + threadIdx.y;
unsigned int idx = iy * nx + ix;
printf("thread_id :(%d,%d) block_id:(%d,%d),coordinate:(%d,%d) global_index :%2d,ival :%2f\n",threadIdx.x,threadIdx.y,blockIdx.x,blockIdx.y,ix,iy,idx,a[idx]);
}
void SumOnHost(float* a, float* b,float*c ,const int nx, const int ny) {
float* ia = a;
float* ib = b;
float* ic = c;
for(int i = 0; i < ny; ++i) {
for(int j = 0; j < nx; ++j) {
ic[j] = ia[j] + ib[j];
}
ic += nx;
ib += nx;
ia += nx;
}
}
__global__ void SumOnDevice(float* a, float* b,float* c,const int nx,const int ny) {
unsigned int ix = blockIdx.x * blockDim.x + threadIdx.x;
unsigned int iy = blockIdx.y * blockDim.y + threadIdx.y;
int idx = iy * nx + ix;
if(ix < nx && iy < ny) {
c[idx] = a[idx] + b[idx];
}
}
void checkResult(float* hostRef, float* gpuRef,const int n) {
double ep = 1.0E-8;
bool match = 1;
for(int i = 0; i < n; ++i) {
if(abs(hostRef[i] - gpuRef[i]) > ep) {
match = 0;
printf("array not match\n");
break;
}
}
if(match) {
printf("array match\n");
}
}
int main(int argc,char** argv) {
int dimx = 16,dimy = 16;
if(argc > 1) {
dimx = atoi(argv[1]);
}
if(argc > 2) {
dimy = atoi(argv[1]);
}
int dev = 0;
cudaDeviceProp deviceProp;
CHECK(cudaGetDeviceProperties( &deviceProp,dev ));
printf("using device :%d\t name :%s \n",dev,deviceProp.name);
CHECK(cudaSetDevice(dev));
int nx = 1 << 14;
int ny = 1 << 14;
int nxy = nx * ny;
int nBytes = nxy * sizeof(float);
float *h_a,*h_b,*hostRef,*gpuRef;
h_a = (float*) malloc(nBytes);
h_b = (float*) malloc(nBytes);
hostRef = (float*) malloc(nBytes);
gpuRef = (float*) malloc(nBytes);
memset(h_a,0,nBytes);
init_data(h_a,nxy);
init_data(h_b,nxy);
memset(hostRef,0,nBytes);
double start = clock();
SumOnHost(h_a,h_b,hostRef,nx,ny);
double end = clock();
double cpu_time_used = (double)(end - start) / CLOCKS_PER_SEC;
printf("cpu cost : %f sec\n",cpu_time_used);
// print_check(h_a,nx,ny);
float* d_a,*d_b,*d_c;
cudaMalloc( (void**)&d_a,nBytes);
cudaMemcpy( d_a, h_a,nBytes , cudaMemcpyHostToDevice);
cudaMalloc( (void**)&d_b,nBytes);
cudaMemcpy( d_b, h_b,nBytes , cudaMemcpyHostToDevice);
cudaMalloc( (void**)&d_c,nBytes);
dim3 block(dimx,dimy);
dim3 grid((nx + block.x - 1)/block.x, (ny + block.y -1)/ block.y);
start = clock();
printf("grid:<%d,%d>,block:<%d,%d>\n",grid.x,grid.y,block.x,block.y);//(32,32) cost:0.88 (32,16) cost:1.89s (16,32):0.8 (16,16) cost:1.34s
SumOnDevice<<<grid,block>>>(d_a,d_b,d_c,nx,ny);
cudaDeviceSynchronize();
CHECK(cudaGetLastError());
cudaMemcpy(gpuRef,d_c ,nBytes , cudaMemcpyDeviceToHost);
end = clock();
double gpu_time_used = (double)(end - start) / CLOCKS_PER_SEC;
printf("gpu cost : %f sec\n",gpu_time_used);
checkResult(hostRef,gpuRef,nxy);
cudaFree( d_a);
cudaFree( d_b);
cudaFree( d_c);
free(h_a);
free(h_b);
free(hostRef);
free(gpuRef);
cudaDeviceReset();
return 0;
}
3.3.1 查看线程束的活跃度
ncu --metrics sm__warps_active.avg.per_cycle_elapsed ./your_cuda_program
- sm__warps_active.avg.per_cycle_elapsed:表示在 kernel 执行期间,每个周期平均有多少个活跃 warp(执行状态)。
解读:数值越高,SM 的并行资源利用越充分。比如一个 SM 理论上可以同时运行 64 个 warp,如果这个值是 16,就只有 25% 利用率。
如果太低,优化策略?
优化策略 | 说明 |
---|---|
✅ 避免线程分歧 | 改写条件逻辑,让一个 warp 中线程尽量走同一条执行路径 |
✅ 增大线程块(如 dim3(256) ) |
通常建议 block 内线程数为 128--1024,最好是 32 的倍数 |
✅ 使用 cudaOccupancyMaxPotentialBlockSize |
自动帮你算出最优 block size |
✅ 减少寄存器/共享内存占用 | 使用 __launch_bounds__ 限制线程使用资源,换取更高占用率 |
✅ 多并发 kernel(高级) | 用多个流(stream)提高 GPU 利用率(适合小 kernel) |
c
// #include <stdio.h>
// #include <cuda_runtime.h>
// cudaError_t cudaOccupancyMaxPotentialBlockSize(
// int* minGridSize, // 输出:推荐的最小 grid size
// int* blockSize, // 输出:推荐的 block size(线程数)
// const void* func, // kernel 函数指针
// size_t dynamicSMemSize = 0, // 每个 block 动态共享内存大小(单位:字节)
// int blockSizeLimit = 0 // 限制最大 block size(0 表示不限)
// );
// // 简单的 CUDA 核函数:每个元素乘以 2
// __global__ void myKernel(float* data) {
// int idx = threadIdx.x + blockIdx.x * blockDim.x;
// if (idx < 100000) { // 防止越界
// data[idx] *= 2.0f;
// }
// }
// int main() {
// const int numElements = 100000;
// size_t size = numElements * sizeof(float);
// // 1. 分配 host 和 device 内存
// float* h_data = (float*)malloc(size);
// float* d_data;
// cudaMalloc((void**)&d_data, size);
// // 2. 初始化 host 数据
// for (int i = 0; i < numElements; ++i) {
// h_data[i] = 1.0f;
// }
// // 3. 拷贝数据到 device
// cudaMemcpy(d_data, h_data, size, cudaMemcpyHostToDevice);
// // 4. 使用 cudaOccupancyMaxPotentialBlockSize 获取最佳 blockSize
// int minGridSize, blockSize;
// cudaOccupancyMaxPotentialBlockSize(
// &minGridSize,
// &blockSize,
// myKernel,
// 0 // 动态共享内存
// );
// int gridSize = (numElements + blockSize - 1) / blockSize;
// printf("Recommended block size: %d\n", blockSize);
// printf("Calculated grid size: %d\n", gridSize);
// // 5. 启动 kernel
// myKernel<<<gridSize, blockSize>>>(d_data);
// cudaDeviceSynchronize();
// // 6. 拷贝结果回 host
// cudaMemcpy(h_data, d_data, size, cudaMemcpyDeviceToHost);
// // 7. 打印部分结果验证
// printf("Result[0] = %f\n", h_data[0]); // 应该是 2.0
// printf("Result[99999] = %f\n", h_data[99999]); // 也是 2.0
// // 8. 释放资源
// free(h_data);
// cudaFree(d_data);
// return 0;
// }
#include <stdio.h>
#include <cuda_runtime.h>
__global__ void myKernel2D(float* data, int width, int height) {
int x = threadIdx.x + blockIdx.x * blockDim.x;
int y = threadIdx.y + blockIdx.y * blockDim.y;
int idx = y * width + x;
if (x < width && y < height) {
data[idx] *= 2.0f;
}
}
int main() {
int width = 512;
int height = 512;
int numElements = width * height;
size_t size = numElements * sizeof(float);
float* h_data = (float*)malloc(size);
float* d_data;
cudaMalloc(&d_data, size);
for (int i = 0; i < numElements; ++i) h_data[i] = 1.0f;
cudaMemcpy(d_data, h_data, size, cudaMemcpyHostToDevice);
// 获取推荐 blockSize
int minGridSize, blockSize;
cudaOccupancyMaxPotentialBlockSize(
&minGridSize, &blockSize, myKernel2D, 0
);
printf("Recommended blockSize = %d\n", blockSize);//只能算出dimx* dimy的值,具体得你自己去实验
// 把 blockSize 拆成二维,如 (16, 16)
dim3 blockDim(16, blockSize / 16); // 例如 blockSize=256 -> (16, 16)
dim3 gridDim(
(width + blockDim.x - 1) / blockDim.x,
(height + blockDim.y - 1) / blockDim.y
);
myKernel2D<<<gridDim, blockDim>>>(d_data, width, height);
cudaDeviceSynchronize();
cudaMemcpy(h_data, d_data, size, cudaMemcpyDeviceToHost);
printf("Result[0] = %f\n", h_data[0]);
free(h_data);
cudaFree(d_data);
return 0;
}
3.3.2 检查内存操作的效率
ncu --metrics dram__throughput.avg.pct_of_peak_sustained_active ./your_cuda_program
- dram__throughput.avg.pct_of_peak_sustained_active:表示 DRAM 实际读取带宽相对于设备峰值带宽的使用百分比。
解读:越高越好,低于 50% 通常说明你可能存在访存模式不合并、不连续、缓存不命中等问题。
优化策略 | 效果 |
---|---|
✅ 确保全局内存访问合并 | 让线程顺序访问相邻地址,确保内存访问 coalesced |
✅ 使用共享内存(__shared__ ) |
把全局读的数据加载到共享内存,线程间复用 |
✅ 数据对齐(align to 32/64 bytes) | 避免 misaligned 访问 |
✅ 避免 bank conflict | 共享内存访问时,避免多个线程访问同一个 memory bank |
✅ 压缩结构体(__align__ , __pack__ ) |
节省不必要的 padding 空间 |
3.4 避免分支分化
3.4.1 并行规约问题
假设:要对一个有N个元素的整数数组求和:
int sum =0;
for(int i = 0 ;i < N; ++i) {
sum += array[i];
}
如何加速呢?
- 将输入向量划分到更小的数据块中。
- 用一个线程计算出一个数据块的部分和。
- 对每个数据块的部分和再求和得出最终结果。
根据每次迭代后输出元素就地存储的位置,成对的并行求和实现可以被进一步分成以下两种类型:
- 相邻配对:元素与它们直接相邻的元素配对
- 交错配对:根据给定的跨度配对元素
int recursiveReduce(float* data,const int size) {
if(size == 1) return data[0];
const int stride = size / 2;
for(int i = 0; i < stride; ++i) {
data[i] += data[i + stride];
}
return recursiveReduce(data,stride);
}
如果可以并行的话,这段代码是很快的,比如我把其分为256个block的话,我只需要再计算完成后对这256的block 求和即可,速度还是特别快的。
规约问题:再向量中满足交换律与结合律的运算,被称为规约问题。
3.4.2 并行规约中的分化
有两个全局数组,一个存储的是整个数组,进行规约,另一个存储的是每个线程块的部分和。每个线程块再数组的一部分上执行操作。__syncthreads 语句可以保证,线程块中的任一线程在进入下一次迭代之前,在当前迭代里的所有部分和都被保存在了全局内存中。进入下一次迭代的所有线程都使用的是上一步所产生的数值。在最后一次循环后,整个线程块的和被保存在了全局内存中。
c
#include <stdio.h>
#include <cuda_runtime.h>
#include <sys/time.h>
double cpuSecond() {
struct timeval tp;
gettimeofday(&tp, NULL);
return ((double)tp.tv_sec + (double)tp.tv_usec * 1e-6);
}
// 设备端归约 kernel(邻居合并法)
__global__ void reduceNeighbored(int* g_idata, int* g_odata, unsigned int size) {
unsigned int tid = threadIdx.x;
int* idata = g_idata + blockIdx.x * blockDim.x;
int idx = blockIdx.x * blockDim.x + tid;
// 边界检查
if (idx >= size) return;
// 归约:邻居合并法
for (int stride = 1; stride < blockDim.x; stride *= 2) {
if (tid % (2 * stride) == 0 && (tid + stride) < blockDim.x) {
idata[tid] += idata[tid + stride];
}
__syncthreads();
}
// 写出每个 block 的结果
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
// CPU 递归归约
int recursiveReduce(int* data, const int size) {
if (size == 1) return data[0];
int stride = size / 2;
for (int i = 0; i < stride; ++i) {
data[i] += data[i + stride];
}
return recursiveReduce(data, stride);
}
int main(int argc, char** argv) {
int dev = 0;
cudaDeviceProp iProp;
cudaGetDeviceProperties(&iProp, dev);
cudaSetDevice(dev);
int size = 1 << 24; // 16777216
printf("array size: %d\n", size);
int blocksize = 512;
if (argc > 1) {
blocksize = atoi(argv[1]);
}
dim3 block(blocksize);
dim3 grid((size + block.x - 1) / block.x);
size_t nBytes = sizeof(int) * size;
int* h_idata = (int*)malloc(nBytes);
int* h_odata = (int*)malloc(grid.x * sizeof(int)); // 只需 grid.x 大小
int* tmp = (int*)malloc(nBytes);
for (int i = 0; i < size; ++i) {
h_idata[i] = (int)(rand() & 0xFF);
}
memcpy(tmp, h_idata, nBytes);
// CPU 归约
double start = cpuSecond();
int cpu_sum = recursiveReduce(tmp, size);
double end = cpuSecond();
printf("CPU sum: %d\t Time: %.4f s\n", cpu_sum, (end - start));
// GPU 归约
int* d_idata = NULL;
int* d_odata = NULL;
cudaMalloc((void**)&d_idata, nBytes);
cudaMalloc((void**)&d_odata, grid.x * sizeof(int));
cudaMemcpy(d_idata, h_idata, nBytes, cudaMemcpyHostToDevice);
cudaDeviceSynchronize();
start = cpuSecond();
reduceNeighbored<<<grid, block>>>(d_idata, d_odata, size);
cudaDeviceSynchronize(); // 保证 kernel 执行完成
end = cpuSecond();
//为啥只取gird.x 的数据呢?因为 kernel 每个 block 只算出自己 block 内的数据总和,而你要获得整个数组的总和,就必须对所有 grid.x 个 block 的部分和再进行一次合并。
cudaMemcpy(h_odata, d_odata, grid.x * sizeof(int), cudaMemcpyDeviceToHost);
int gpu_sum = 0;
for (int i = 0; i < grid.x; ++i) {
gpu_sum += h_odata[i];
}
printf("GPU sum: %d\t Time: %.4f s\n", gpu_sum, (end - start));
// 校验结果
bool result = (gpu_sum == cpu_sum);
printf(result ? "Test PASSED\n" : "Test FAILED\n");
free(h_idata);
free(h_odata);
free(tmp);
cudaFree(d_idata);
cudaFree(d_odata);
cudaDeviceReset();
return 0;
}
array size: 16777216
CPU sum: 2139353471 Time: 0.0407 s
GPU sum: 2139353471 Time: 0.0005 s
Test PASSED
3.4.3 改善并行规约中的分化
if (tid % (2 * stride) == 0 && (tid + stride) < blockDim.x) {
idata[tid] += idata[tid + stride];
}
这个代码:tid % (2 * stride) 注定了只有偶数的线程号才可以被调用,也就是说,理想情况下,只有一半的线程参与计算,这在程序层面来说是不合适的。如何改善?
c
#include <stdio.h>
#include <cuda_runtime.h>
#include <sys/time.h>
double cpuSecond() {
struct timeval tp;
gettimeofday(&tp, NULL);
return ((double)tp.tv_sec + (double)tp.tv_usec * 1e-6);
}
// 设备端归约 kernel(邻居合并法)
__global__ void reduceNeighbored(int* g_idata, int* g_odata, unsigned int size) {
unsigned int tid = threadIdx.x;
int* idata = g_idata + blockIdx.x * blockDim.x;
int idx = blockIdx.x * blockDim.x + tid;
// 边界检查
if (idx >= size) return;
// 归约:邻居合并法
for (int stride = 1; stride < blockDim.x; stride *= 2) {
if (tid % (2 * stride) == 0 && (tid + stride) < blockDim.x) {
idata[tid] += idata[tid + stride];
}
__syncthreads();
}
// 写出每个 block 的结果
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
//减少并行规约了,但是内存不连续了,所以慢点,但也比之前的块
__global__ void reduceNeighboredLess(int* g_idata, int* g_odata, unsigned int size) {
unsigned int tid = threadIdx.x;
int* idata = g_idata + blockIdx.x * blockDim.x;
int idx = blockIdx.x * blockDim.x + tid;
// 边界检查
if (idx >= size) return;
// 归约:邻居合并法
for (int stride = 1; stride < blockDim.x; stride *= 2) {
int index = 2 * stride * tid;
if(index < blockDim.x) {
idata[index] += idata[index + stride];
}
__syncthreads();
}
// 写出每个 block 的结果
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
// CPU 递归归约
int recursiveReduce(int* data, const int size) {
if (size == 1) return data[0];
int stride = size / 2;
for (int i = 0; i < stride; ++i) {
data[i] += data[i + stride];
}
return recursiveReduce(data, stride);
}
int main(int argc, char** argv) {
int dev = 0;
cudaDeviceProp iProp;
cudaGetDeviceProperties(&iProp, dev);
cudaSetDevice(dev);
int size = 1 << 24; // 16777216
printf("array size: %d\n", size);
int blocksize = 512;
if (argc > 1) {
blocksize = atoi(argv[1]);
}
dim3 block(blocksize);
dim3 grid((size + block.x - 1) / block.x);
size_t nBytes = sizeof(int) * size;
int* h_idata = (int*)malloc(nBytes);
int* h_odata = (int*)malloc(grid.x * sizeof(int)); // 只需 grid.x 大小
int* tmp = (int*)malloc(nBytes);
for (int i = 0; i < size; ++i) {
h_idata[i] = (int)(rand() & 0xFF);
}
memcpy(tmp, h_idata, nBytes);
// CPU 归约
double start = cpuSecond();
int cpu_sum = recursiveReduce(tmp, size);
double end = cpuSecond();
printf("CPU sum: %d\t Time: %.4f s\n", cpu_sum, (end - start));
// GPU 归约
int* d_idata = NULL;
int* d_odata = NULL;
cudaMalloc((void**)&d_idata, nBytes);
cudaMalloc((void**)&d_odata, grid.x * sizeof(int));
cudaMemcpy(d_idata, h_idata, nBytes, cudaMemcpyHostToDevice);
cudaDeviceSynchronize();
// start = cpuSecond();
// reduceNeighbored<<<grid, block>>>(d_idata, d_odata, size);
// cudaDeviceSynchronize(); // 保证 kernel 执行完成
// end = cpuSecond();
// cudaMemcpy(h_odata, d_odata, grid.x * sizeof(int), cudaMemcpyDeviceToHost);
int gpu_sum = 0;
// for (int i = 0; i < grid.x; ++i) {
// gpu_sum += h_odata[i];
// }
// printf("GPU sum: %d\t Time: %.4f s\n", gpu_sum, (end - start));
start = cpuSecond();
reduceNeighboredLess<<<grid, block>>>(d_idata, d_odata, size);
cudaDeviceSynchronize(); // 保证 kernel 执行完成
end = cpuSecond();
cudaMemcpy(h_odata, d_odata, grid.x * sizeof(int), cudaMemcpyDeviceToHost);
gpu_sum = 0;
for (int i = 0; i < grid.x; ++i) {
gpu_sum += h_odata[i];
}
printf("GPU sum: %d\t Time: %.4f s\n", gpu_sum, (end - start));
// 校验结果
bool result = (gpu_sum == cpu_sum);
printf(result ? "Test PASSED\n" : "Test FAILED\n");
free(h_idata);
free(h_odata);
free(tmp);
cudaFree(d_idata);
cudaFree(d_odata);
cudaDeviceReset();
return 0;
}
上一个gpu是0.0005s
下一个方法是0.0003s
3.4.4 交错配对的规约
c
#include <stdio.h>
#include <cuda_runtime.h>
#include <sys/time.h>
double cpuSecond() {
struct timeval tp;
gettimeofday(&tp, NULL);
return ((double)tp.tv_sec + (double)tp.tv_usec * 1e-6);
}
// 设备端归约 kernel(邻居合并法)
__global__ void reduceNeighbored(int* g_idata, int* g_odata, unsigned int size) {
unsigned int tid = threadIdx.x;
int* idata = g_idata + blockIdx.x * blockDim.x;
int idx = blockIdx.x * blockDim.x + tid;
// 边界检查
if (idx >= size) return;
// 归约:邻居合并法
for (int stride = 1; stride < blockDim.x; stride *= 2) {
if (tid % (2 * stride) == 0 && (tid + stride) < blockDim.x) {
idata[tid] += idata[tid + stride];
}
__syncthreads();
}
// 写出每个 block 的结果
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
__global__ void reduceInterLevel(int* g_idata, int* g_odata, unsigned int size) {
unsigned int tid = threadIdx.x;
int* idata = g_idata + blockIdx.x * blockDim.x;
int idx = blockIdx.x * blockDim.x + tid;
// 边界检查
if (idx >= size) return;
// 归约:邻居合并法
//第一次规约1/2 第二次 1/4依次类推,速度越来越快,耗时0.0002s
for (int stride = blockDim.x / 2; stride > 0; stride >>= 1) {
if(tid < stride) {
idata[tid] += idata[tid + stride];
}
__syncthreads();
}
// 写出每个 block 的结果
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
// CPU 递归归约
int recursiveReduce(int* data, const int size) {
if (size == 1) return data[0];
int stride = size / 2;
for (int i = 0; i < stride; ++i) {
data[i] += data[i + stride];
}
return recursiveReduce(data, stride);
}
int main(int argc, char** argv) {
int dev = 0;
cudaDeviceProp iProp;
cudaGetDeviceProperties(&iProp, dev);
cudaSetDevice(dev);
int size = 1 << 24; // 16777216
printf("array size: %d\n", size);
int blocksize = 512;
if (argc > 1) {
blocksize = atoi(argv[1]);
}
dim3 block(blocksize);
dim3 grid((size + block.x - 1) / block.x);
size_t nBytes = sizeof(int) * size;
int* h_idata = (int*)malloc(nBytes);
int* h_odata = (int*)malloc(grid.x * sizeof(int)); // 只需 grid.x 大小
int* tmp = (int*)malloc(nBytes);
for (int i = 0; i < size; ++i) {
h_idata[i] = (int)(rand() & 0xFF);
}
memcpy(tmp, h_idata, nBytes);
// CPU 归约
double start = cpuSecond();
int cpu_sum = recursiveReduce(tmp, size);
double end = cpuSecond();
printf("CPU sum: %d\t Time: %.4f s\n", cpu_sum, (end - start));
// GPU 归约
int* d_idata = NULL;
int* d_odata = NULL;
cudaMalloc((void**)&d_idata, nBytes);
cudaMalloc((void**)&d_odata, grid.x * sizeof(int));
cudaMemcpy(d_idata, h_idata, nBytes, cudaMemcpyHostToDevice);
cudaDeviceSynchronize();
// start = cpuSecond();
// reduceNeighbored<<<grid, block>>>(d_idata, d_odata, size);
// cudaDeviceSynchronize(); // 保证 kernel 执行完成
// end = cpuSecond();
// cudaMemcpy(h_odata, d_odata, grid.x * sizeof(int), cudaMemcpyDeviceToHost);
int gpu_sum = 0;
// for (int i = 0; i < grid.x; ++i) {
// gpu_sum += h_odata[i];
// }
// printf("GPU sum: %d\t Time: %.4f s\n", gpu_sum, (end - start));
cudaMemset( d_odata, 0, grid.x * sizeof(int));
start = cpuSecond();
reduceInterLevel<<<grid, block>>>(d_idata, d_odata, size);
cudaDeviceSynchronize(); // 保证 kernel 执行完成
end = cpuSecond();
cudaMemcpy(h_odata, d_odata, grid.x * sizeof(int), cudaMemcpyDeviceToHost);
gpu_sum = 0;
for (int i = 0; i < grid.x; ++i) {
gpu_sum += h_odata[i];
}
printf("GPU sum: %d\t Time: %.4f s\n", gpu_sum, (end - start));
// 校验结果
bool result = (gpu_sum == cpu_sum);
printf(result ? "Test PASSED\n" : "Test FAILED\n");
free(h_idata);
free(h_odata);
free(tmp);
cudaFree(d_idata);
cudaFree(d_odata);
cudaDeviceReset();
return 0;
}
3.5 展开循环
for(int i = 0; i < 100; ++i) {
a[i] = b[i] + c[i];
}
for(int i = 0; i < 100; i += 2) {
a[i] = b[i] + c[i];
a[i+1] = b[i + 1] + c[i+1];
}
优化目标:通过减少指令消耗和增加更多的独立指令来提高性能。因此,更多的并发操作被添加到流水线上,以产生更高的指令来提高性能。
3.5.1 展开的规约
只需要一半的线程,速度提升一倍相比之前。
c
#include <stdio.h>
#include <cuda_runtime.h>
#include <sys/time.h>
double cpuSecond() {
struct timeval tp;
gettimeofday(&tp, NULL);
return ((double)tp.tv_sec + (double)tp.tv_usec * 1e-6);
}
//展开的规约
__global__ void reduceUnrolling2(int* g_idata, int* g_odata, unsigned int size) {
unsigned int tid = threadIdx.x;
int* idata = g_idata + blockIdx.x * blockDim.x * 2;
int idx = blockIdx.x * blockDim.x * 2 + tid;
//在这里。每个线程都添加一个来自于相邻数据块的元素
if(idx + blockDim.x < size) {
g_idata[idx] += g_idata[idx + blockDim.x];
}
__syncthreads();
for (int stride = blockDim.x / 2; stride > 0 ; stride >>= 1) {
if(tid < stride) {
idata[tid] += idata[tid + stride];
}
__syncthreads();
}
// 写出每个 block 的结果
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
__global__ void reduceInterLevel(int* g_idata, int* g_odata, unsigned int size) {
unsigned int tid = threadIdx.x;
int* idata = g_idata + blockIdx.x * blockDim.x;
int idx = blockIdx.x * blockDim.x + tid;
// 边界检查
if (idx >= size) return;
// 归约:邻居合并法
//第一次规约1/2 第二次 1/4依次类推,速度越来越快,耗时0.0002s
for (int stride = blockDim.x / 2; stride > 0; stride >>= 1) {
if(tid < stride) {
idata[tid] += idata[tid + stride];
}
__syncthreads();
}
// 写出每个 block 的结果
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
// CPU 递归归约
int recursiveReduce(int* data, const int size) {
if (size == 1) return data[0];
int stride = size / 2;
for (int i = 0; i < stride; ++i) {
data[i] += data[i + stride];
}
return recursiveReduce(data, stride);
}
int main(int argc, char** argv) {
int dev = 0;
cudaDeviceProp iProp;
cudaGetDeviceProperties(&iProp, dev);
cudaSetDevice(dev);
int size = 1 << 24; // 16777216
printf("array size: %d\n", size);
int blocksize = 512;
if (argc > 1) {
blocksize = atoi(argv[1]);
}
dim3 block(blocksize);
dim3 grid((size + block.x - 1) / block.x);
size_t nBytes = sizeof(int) * size;
int* h_idata = (int*)malloc(nBytes);
int* h_odata = (int*)malloc(grid.x * sizeof(int)); // 只需 grid.x 大小
int* tmp = (int*)malloc(nBytes);
for (int i = 0; i < size; ++i) {
h_idata[i] = (int)(rand() & 0xFF);
}
memcpy(tmp, h_idata, nBytes);
// CPU 归约
double start = cpuSecond();
int cpu_sum = recursiveReduce(tmp, size);
double end = cpuSecond();
printf("CPU sum: %d\t Time: %.4f s\n", cpu_sum, (end - start));
// GPU 归约
int* d_idata = NULL;
int* d_odata = NULL;
cudaMalloc((void**)&d_idata, nBytes);
cudaMalloc((void**)&d_odata, grid.x * sizeof(int));
cudaMemcpy(d_idata, h_idata, nBytes, cudaMemcpyHostToDevice);
cudaDeviceSynchronize();
// start = cpuSecond();
// reduceNeighbored<<<grid, block>>>(d_idata, d_odata, size);
// cudaDeviceSynchronize(); // 保证 kernel 执行完成
// end = cpuSecond();
// cudaMemcpy(h_odata, d_odata, grid.x * sizeof(int), cudaMemcpyDeviceToHost);
int gpu_sum = 0;
// for (int i = 0; i < grid.x; ++i) {
// gpu_sum += h_odata[i];
// }
// printf("GPU sum: %d\t Time: %.4f s\n", gpu_sum, (end - start));
cudaMemset( d_odata, 0, grid.x * sizeof(int));
start = cpuSecond();
reduceUnrolling2<<<grid.x / 2 , block>>>(d_idata, d_odata, size);
cudaDeviceSynchronize(); // 保证 kernel 执行完成
end = cpuSecond();
cudaMemcpy(h_odata, d_odata, grid.x /2 * sizeof(int), cudaMemcpyDeviceToHost);
gpu_sum = 0;
for (int i = 0; i < grid.x / 2; ++i) {
gpu_sum += h_odata[i];
}
printf("GPU sum: %d\t Time: %.4f s\n", gpu_sum, (end - start));
// 校验结果
bool result = (gpu_sum == cpu_sum);
printf(result ? "Test PASSED\n" : "Test FAILED\n");
free(h_idata);
free(h_odata);
free(tmp);
cudaFree(d_idata);
cudaFree(d_odata);
cudaDeviceReset();
return 0;
}
3.5.2 展开线程的规约
#include <stdio.h>
#include <cuda_runtime.h>
#include <sys/time.h>
double cpuSecond() {
struct timeval tp;
gettimeofday(&tp, NULL);
return ((double)tp.tv_sec + (double)tp.tv_usec * 1e-6);
}
//展开的规约
__global__ void reduceUnrollWarps8(int* g_idata, int* g_odata, unsigned int size) {
unsigned int tid = threadIdx.x;
int* idata = g_idata + blockIdx.x * blockDim.x * 8;
int idx = blockIdx.x * blockDim.x *8 + tid;
//在这里。每个线程都添加一个来自于相邻数据块的元素
if(idx + 7 * blockDim.x < size) {
int a1= g_idata[idx];
int a2= g_idata[idx + 1 * blockDim.x];
int a3= g_idata[idx + 2 * blockDim.x];
int a4= g_idata[idx + 3 * blockDim.x];
int a5= g_idata[idx + 4 * blockDim.x];
int a6= g_idata[idx + 5 * blockDim.x];
int a7= g_idata[idx + 6 * blockDim.x];
int a8= g_idata[idx + 7 * blockDim.x];
g_idata[idx] = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8;
}
__syncthreads();
for (int stride = blockDim.x / 2; stride > 32 ; stride >>= 1) {
if(tid < stride) {
idata[tid] += idata[tid + stride];
}
__syncthreads();
}
if(tid < 32) {
volatile int* vem = idata;
vem[tid] += vem[tid + 32];
vem[tid] += vem[tid + 16];
vem[tid] += vem[tid + 8];
vem[tid] += vem[tid + 4];
vem[tid] += vem[tid + 2];
vem[tid] += vem[tid + 1];
}
// 写出每个 block 的结果
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
// CPU 递归归约
int recursiveReduce(int* data, const int size) {
if (size == 1) return data[0];
int stride = size / 2;
for (int i = 0; i < stride; ++i) {
data[i] += data[i + stride];
}
return recursiveReduce(data, stride);
}
int main(int argc, char** argv) {
int dev = 0;
cudaDeviceProp iProp;
cudaGetDeviceProperties(&iProp, dev);
cudaSetDevice(dev);
int size = 1 << 24; // 16777216
printf("array size: %d\n", size);
int blocksize = 512;
if (argc > 1) {
blocksize = atoi(argv[1]);
}
dim3 block(blocksize);
dim3 grid((size + block.x - 1) / block.x);
size_t nBytes = sizeof(int) * size;
int* h_idata = (int*)malloc(nBytes);
int* h_odata = (int*)malloc(grid.x * sizeof(int)); // 只需 grid.x 大小
int* tmp = (int*)malloc(nBytes);
for (int i = 0; i < size; ++i) {
h_idata[i] = (int)(rand() & 0xFF);
}
memcpy(tmp, h_idata, nBytes);
// CPU 归约
double start = cpuSecond();
int cpu_sum = recursiveReduce(tmp, size);
double end = cpuSecond();
printf("CPU sum: %d\t Time: %.4f s\n", cpu_sum, (end - start));
// GPU 归约
int* d_idata = NULL;
int* d_odata = NULL;
cudaMalloc((void**)&d_idata, nBytes);
cudaMalloc((void**)&d_odata, grid.x * sizeof(int));
cudaMemcpy(d_idata, h_idata, nBytes, cudaMemcpyHostToDevice);
cudaDeviceSynchronize();
// start = cpuSecond();
// reduceNeighbored<<<grid, block>>>(d_idata, d_odata, size);
// cudaDeviceSynchronize(); // 保证 kernel 执行完成
// end = cpuSecond();
// cudaMemcpy(h_odata, d_odata, grid.x * sizeof(int), cudaMemcpyDeviceToHost);
int gpu_sum = 0;
// for (int i = 0; i < grid.x; ++i) {
// gpu_sum += h_odata[i];
// }
// printf("GPU sum: %d\t Time: %.4f s\n", gpu_sum, (end - start));
cudaMemset( d_odata, 0, grid.x * sizeof(int));
start = cpuSecond();
reduceUnrollWarps8<<<grid.x / 8 , block>>>(d_idata, d_odata, size);
cudaDeviceSynchronize(); // 保证 kernel 执行完成
end = cpuSecond();
cudaMemcpy(h_odata, d_odata, grid.x /8 * sizeof(int), cudaMemcpyDeviceToHost);
gpu_sum = 0;
for (int i = 0; i < grid.x / 8; ++i) {
gpu_sum += h_odata[i];
}
printf("GPU sum: %d\t Time: %.4f s\n", gpu_sum, (end - start));
// 校验结果
bool result = (gpu_sum == cpu_sum);
printf(result ? "Test PASSED\n" : "Test FAILED\n");
free(h_idata);
free(h_odata);
free(tmp);
cudaFree(d_idata);
cudaFree(d_odata);
cudaDeviceReset();
return 0;
}