在CUDA编程中,共享内存(Shared Memory) 比 全局内存(Global Memory) 效率高的原因主要与CUDA的硬件架构和内存访问特性密切相关。以下是详细分析:
CUDA设备(GPU)具有多层次的内存架构,主要包括以下几种:
共享内存是CUDA中一种非常重要的资源,其高效性主要体现在以下几个方面。
因此,共享内存的访问速度远高于全局内存。
在CUDA编程中,使用共享内存的主要目的是优化数据访问模式,减少对全局内存的依赖。以下是一些典型的应用场景及其优化原理:
以下是一个简单的矩阵乘法示例,展示如何利用共享内存优化性能:
__global__ void matrixMulSharedMemory(float* A, float* B, float* C, int N) {
// 定义共享内存
__shared__ float sharedA[TILE_SIZE][TILE_SIZE];
__shared__ float sharedB[TILE_SIZE][TILE_SIZE];
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
float result = 0.0f;
// 分块计算
for (int tile = 0; tile < (N + TILE_SIZE - 1) / TILE_SIZE; tile++) {
// 将数据加载到共享内存中
if (row < N && tile * TILE_SIZE + threadIdx.x < N)
sharedA[threadIdx.y][threadIdx.x] = A[row * N + tile * TILE_SIZE + threadIdx.x];
else
sharedA[threadIdx.y][threadIdx.x] = 0.0f;
if (col < N && tile * TILE_SIZE + threadIdx.y < N)
sharedB[threadIdx.y][threadIdx.x] = B[(tile * TILE_SIZE + threadIdx.y) * N + col];
else
sharedB[threadIdx.y][threadIdx.x] = 0.0f;
__syncthreads(); // 确保所有线程完成加载
// 计算当前分块的结果
for (int k = 0; k < TILE_SIZE; k++) {
result += sharedA[threadIdx.y][k] * sharedB[k][threadIdx.x];
}
__syncthreads(); // 确保所有线程完成计算
}
// 写回结果到全局内存
if (row < N && col < N)
C[row * N + col] = result;
}
sharedA
和sharedB
中。sharedA
和sharedB
中的数据,避免了重复加载。__syncthreads()
确保所有线程完成共享内存的加载和计算后再继续下一步。虽然共享内存能显著提高性能,但在使用时需要注意以下几点:
在CUDA编程中,共享内存比全局内存效率高的核心原因在于其低访问延迟、高带宽、数据重用以及更好的局部性。通过合理利用共享内存,可以显著减少对全局内存的访问次数,优化内存访问模式,从而提高程序的整体性能。
下面是一个矩阵乘法的示例,比较了使用共享内存和使用全局内存两种方式的性能差异:
/*
* 矩阵乘法性能对比示例
*
* 本程序实现了两种矩阵乘法的方法:
* 1. 使用全局内存的朴素实现
* - 每个线程直接从全局内存读取数据
* - 重复访问全局内存,性能较低
*
* 2. 使用共享内存的优化实现
* - 将矩阵分块加载到共享内存
* - 减少全局内存访问次数
* - 提高内存访问效率
* - 显著提升计算性能
*/
#include
#include
#include
// 矩阵大小(为简化示例,使用方阵)
#define MATRIX_SIZE 1024
// 每个线程块的大小(二维)
#define BLOCK_SIZE 64
// 检查CUDA错误
void checkCudaError(cudaError_t error, const char* message) {
if (error != cudaSuccess) {
fprintf(stderr, "CUDA错误: %s - %s\n", message, cudaGetErrorString(error));
exit(-1);
}
}
// 使用全局内存的矩阵乘法核函数
__global__ void matrixMulGlobal(
const float* A,
const float* B,
float* C,
int size
) {
// 计算当前线程负责的矩阵C中的元素位置
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
if (row < size && col < size) {
float sum = 0.0f;
// 计算一个元素需要遍历一整行和一整列
for (int k = 0; k < size; k++) {
sum += A[row * size + k] * B[k * size + col];
}
C[row * size + col] = sum;
}
}
// 使用共享内存的矩阵乘法核函数
__global__ void matrixMulShared(
const float* A,
const float* B,
float* C,
int size
) {
// 声明共享内存,用于存储A和B的子矩阵
__shared__ float sharedA[BLOCK_SIZE][BLOCK_SIZE];
__shared__ float sharedB[BLOCK_SIZE][BLOCK_SIZE];
// 计算线程在全局和块内的位置
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
int tx = threadIdx.x;
int ty = threadIdx.y;
float sum = 0.0f;
// 分块计算,每次处理BLOCK_SIZE大小的子矩阵
for (int i = 0; i < (size + BLOCK_SIZE - 1) / BLOCK_SIZE; i++) {
// 协作加载数据到共享内存
if (row < size && (i * BLOCK_SIZE + tx) < size) {
sharedA[ty][tx] = A[row * size + i * BLOCK_SIZE + tx];
} else {
sharedA[ty][tx] = 0.0f;
}
if (col < size && (i * BLOCK_SIZE + ty) < size) {
sharedB[ty][tx] = B[(i * BLOCK_SIZE + ty) * size + col];
} else {
sharedB[ty][tx] = 0.0f;
}
// 确保所有线程都完成数据加载,以确保共享内存加载完成
__syncthreads();
// 计算当前子矩阵的点积
if (row < size && col < size) {
for (int k = 0; k < BLOCK_SIZE; k++) {
sum += sharedA[ty][k] * sharedB[k][tx];
}
}
// 同步以确保计算完成后再加载下一块数据
__syncthreads();
}
// 将结果写回全局内存
if (row < size && col < size) {
C[row * size + col] = sum;
}
}
// 初始化矩阵数据
void initMatrix(float* matrix, int size) {
for (int i = 0; i < size * size; i++) {
matrix[i] = rand() / (float)RAND_MAX;
}
}
int main() {
int size = MATRIX_SIZE;
size_t matrixBytes = size * size * sizeof(float);
// 分配主机内存
float *h_A, *h_B, *h_C1, *h_C2;
h_A = (float*)malloc(matrixBytes);
h_B = (float*)malloc(matrixBytes);
h_C1 = (float*)malloc(matrixBytes);
h_C2 = (float*)malloc(matrixBytes);
// 初始化输入矩阵
printf("正在初始化矩阵数据...\n");
initMatrix(h_A, size);
initMatrix(h_B, size);
// 分配设备内存
float *d_A, *d_B, *d_C;
checkCudaError(cudaMalloc((void**)&d_A, matrixBytes), "分配设备内存d_A失败");
checkCudaError(cudaMalloc((void**)&d_B, matrixBytes), "分配设备内存d_B失败");
checkCudaError(cudaMalloc((void**)&d_C, matrixBytes), "分配设备内存d_C失败");
// 将数据复制到设备
checkCudaError(cudaMemcpy(d_A, h_A, matrixBytes, cudaMemcpyHostToDevice), "复制数据到设备d_A失败");
checkCudaError(cudaMemcpy(d_B, h_B, matrixBytes, cudaMemcpyHostToDevice), "复制数据到设备d_B失败");
// 设置网格和块的维度
dim3 blockDim(BLOCK_SIZE, BLOCK_SIZE);
dim3 gridDim((size + BLOCK_SIZE - 1) / BLOCK_SIZE, (size + BLOCK_SIZE - 1) / BLOCK_SIZE);
// 创建CUDA事件用于计时
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
float elapsedTime;
// 运行使用全局内存的版本
printf("\n运行使用全局内存的矩阵乘法...\n");
cudaEventRecord(start);
matrixMulGlobal<<>>(d_A, d_B, d_C, size);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsedTime, start, stop);
printf("使用全局内存的版本耗时: %.2f ms\n", elapsedTime);
// 复制结果到主机
checkCudaError(cudaMemcpy(h_C1, d_C, matrixBytes, cudaMemcpyDeviceToHost), "从设备复制结果失败");
// 运行使用共享内存的版本
float elapsedTime1;
printf("\n运行使用共享内存的矩阵乘法...\n");
cudaEventRecord(start);
matrixMulShared<<>>(d_A, d_B, d_C, size);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsedTime1, start, stop);
printf("使用共享内存的版本耗时: %.2f ms\n", elapsedTime1);
printf("加速比: %.2f\n", elapsedTime / elapsedTime1);
// 复制结果到主机
checkCudaError(cudaMemcpy(h_C2, d_C, matrixBytes, cudaMemcpyDeviceToHost), "从设备复制结果失败");
// 验证两种方法的结果是否一致
bool resultsMatch = true;
for (int i = 0; i < size * size && resultsMatch; i++) {
if (fabs(h_C1[i] - h_C2[i]) > 1e-5) {
resultsMatch = false;
break;
}
}
printf("\n两种实现的结果%s\n", resultsMatch ? "一致" : "不一致");
// 清理资源
cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C);
cudaEventDestroy(start);
cudaEventDestroy(stop);
free(h_A);
free(h_B);
free(h_C1);
free(h_C2);
return 0;
}