CUDA基础知识巩固检验练习题【附有参考答案】(7)

以下是针对 2.2.3.5 Constant Memory2.2.3.8 Distributed Shared Memory 章节内容设计的完整知识点巩固习题包。


常量内存、缓存、纹理内存与分布式共享内存知识巩固习题包

一、选择题(每题只有一个正确答案)

1. 关于常量内存(Constant Memory),下列说法正确的是?

A. 常量内存位于SM内部,与共享内存共享物理空间

B. 常量内存的作用域是线程块级别

C. 常量内存是只读的,且具有缓存优化

D. 常量内存的大小通常为256KB

2. 以下哪个API用于将数据从主机拷贝到常量内存?

A. cudaMemcpy()

B. cudaMemcpyToSymbol()

C. cudaMemcpyToConstant()

D. cudaCopyToConstant()

3. 常量内存的典型大小是多少?

A. 16KB

B. 64KB

C. 256KB

D. 1MB

4. 关于L1缓存和共享内存的关系,下列说法正确的是?

A. 它们是独立的硬件资源

B. 它们共享同一物理空间,使用共享内存会减少L1缓存可用大小

C. L1缓存是共享内存的一部分

D. 它们互不影响,完全独立

5. 以下哪个设备属性用于查询L2缓存大小?

A. sharedMemPerMultiprocessor

B. totalConstMem

C. l2CacheSize

D. regsPerBlock

6. 关于纹理内存(Texture Memory)在现代GPU上的使用建议,下列说法正确的是?

A. 纹理内存仍然是最快的内存类型

B. 纹理内存在所有场景下都优于全局内存

C. 纹理内存不再提供性能优势,新代码应忽略这些API

D. 纹理内存必须用于所有图像处理应用

7. 分布式共享内存(Distributed Shared Memory)是从哪个计算能力开始引入的?

A. 7.0

B. 8.0

C. 9.0

D. 10.0

8. 分布式共享内存的大小如何计算?

A. 每个线程块的共享内存大小

B. 集群大小 × 每线程块共享内存大小

C. 网格大小 × 每线程块共享内存大小

D. 设备共享内存总量

9. 在分布式共享内存中,如何获取指定秩块的共享内存指针?

A. cluster.get_shared_memory(rank)

B. cluster.map_shared_rank(smem, rank)

C. cluster.shared_memory(rank)

D. cluster.get_ptr(rank)

10. 以下哪个函数用于确保集群内所有线程块都已开始执行?

A. __syncthreads()

B. cudaDeviceSynchronize()

C. cluster.sync()

D. cudaStreamSynchronize()

11. 关于常量内存的声明,下列说法正确的是?

A. 必须在内核函数内部使用__constant__声明

B. 必须在任何函数外部使用__constant__声明

C. 可以使用cudaMallocConstant()动态分配

D. 常量内存变量可以在内核中修改

12. 如果内核不使用共享内存,L1缓存会怎样?

A. 仍然保持固定大小

B. 整个物理空间将被L1缓存利用

C. L1缓存被禁用

D. 空间被分配给寄存器使用

13. 在分布式共享内存的直方图示例中,为什么需要使用两次cluster.sync()

A. 第一次同步初始化,第二次同步计算结果

B. 第一次确保所有块初始化完成,第二次确保分布式操作完成

C. 第一次同步数据加载,第二次同步结果写入

D. 不需要两次同步

14. 以下哪个API用于设置内核的最大动态共享内存大小?

A. cudaSetDeviceProperties()

B. cudaFuncSetCacheConfig()

C. cudaFuncSetAttribute()

D. cudaSetSharedMemoryConfig()

15. 在cudaLaunchKernelEx中,如何指定集群维度?

A. 使用<<<grid, block, shared, cluster>>>

B. 使用cudaLaunchAttributeClusterDimension属性

C. 使用cudaSetClusterSize()函数

D. 在内核中使用__cluster_dims__指定

二、填空题

**1. 常量内存使用 _____ 说明符声明,其作用域是 _____ ,生命周期是 _____

**2. 常量内存的大小可以通过设备属性 _____ 查询。

**3. 从主机拷贝数据到常量内存应使用 _____ 函数。

**4. L2缓存位于 _____ 上,被 _____ 共享。

**5. L1缓存与 _____ 共享同一物理空间,位于每个 _____ 内部。

**6. 纹理内存在现代GPU上 不再提供性能优势 ,新代码应 _____ 这些API。

**7. 分布式共享内存从计算能力 _____ 开始引入,与 _____ 特性配合使用。

**8. 分布式共享内存的大小 = _____ × _____

**9. 在分布式共享内存中,使用 _____ 函数确保所有线程块已开始执行。

**10. 获取远程线程块共享内存指针使用 _____ 函数。

**11. 在直方图示例中,如果集群大小为1,表示使用 _____,而不是分布式共享内存。

12. 常量内存适合存储 __________** 的数据。

**13. 设置内核的L1/共享内存偏好可以使用 _____ 函数。

**14. 在cudaLaunchKernelEx中,通过 _____ 属性指定集群维度。

**15. 使用分布式共享内存时,必须确保所有分布式操作在 _____ 之前完成。

三、简答题

1. 说明常量内存的特点、适用场景以及如何使用。

2. 解释L1缓存和共享内存的关系,以及如何配置它们之间的平衡。

3. 为什么在现代GPU上不再推荐使用纹理内存?对于维护老代码的开发者有什么建议?

4. 什么是分布式共享内存?它解决了什么问题?

5. 描述在分布式共享内存中使用cluster.sync()的两个重要时机及其作用。

6. 解释分布式共享内存直方图示例中的cluster.map_shared_rank(smem, dst_block_rank)的作用。

7. 比较常量内存和全局内存在只读数据访问方面的差异。

8. 说明如何根据直方图桶数动态选择集群大小(共享内存 vs 分布式共享内存 vs 全局内存)。

9. 在分布式共享内存编程中,为什么需要确保所有线程块在访问远程共享内存时都处于活动状态?

10. 解释cudaLaunchAttributeClusterDimension的作用和使用方法。

四、分析题

1. 分析以下常量内存使用代码,找出潜在问题并改进:

cpp 复制代码
#include <cuda_runtime.h>
#include <stdio.h>

__constant__ float coefficients[256];

__global__ void applyCoefficients(float* data, int N) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    
    if (idx < N) {
        // 使用系数
        float result = 0.0f;
        for (int i = 0; i < 256; i++) {
            result += data[idx] * coefficients[i];
        }
        data[idx] = result;
    }
}

int main() {
    float* d_data;
    int N = 1000000;
    cudaMalloc(&d_data, N * sizeof(float));
    
    // 初始化主机系数
    float h_coeff[256];
    for (int i = 0; i < 256; i++) {
        h_coeff[i] = (float)i;
    }
    
    // 拷贝到常量内存
    cudaMemcpy(coefficients, h_coeff, sizeof(h_coeff), cudaMemcpyHostToDevice);
    
    // 启动内核
    int threads = 256;
    int blocks = (N + threads - 1) / threads;
    applyCoefficients<<<blocks, threads>>>(d_data, N);
    
    cudaDeviceSynchronize();
    cudaFree(d_data);
    return 0;
}

问题:

  • 代码中存在什么错误?
  • 这个内核设计有什么性能问题?
  • 如何改进?

2. 分析以下分布式共享内存代码,指出可能的错误:

cpp 复制代码
#include <cooperative_groups.h>

__global__ void clusterProcess(int* data, int N) {
    extern __shared__ int smem[];
    namespace cg = cooperative_groups;
    
    cg::cluster_group cluster = cg::this_cluster();
    int cluster_size = cluster.dim_blocks().x;
    int block_rank = cluster.block_rank();
    
    // 初始化本地共享内存
    for (int i = threadIdx.x; i < 64; i += blockDim.x) {
        smem[i] = 0;
    }
    
    // 处理数据
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < N) {
        // 确定目标块
        int target_block = idx % cluster_size;
        
        // 获取目标块共享内存指针
        int* target_smem = cluster.map_shared_rank(smem, target_block);
        
        // 更新目标块共享内存
        target_smem[threadIdx.x] += data[idx];
    }
    
    // 将结果写回全局内存
    if (threadIdx.x == 0) {
        data[block_rank] = smem[0];
    }
}

问题:

  • 代码缺少了什么必要的同步?
  • 为什么需要这些同步?
  • 可能存在什么数据一致性问题?

3. 分析以下缓存配置代码,说明其效果:

cpp 复制代码
__global__ void kernelA(float* data) {
    __shared__ float cache[256];
    // 内核代码
}

__global__ void kernelB(float* data) {
    // 不使用共享内存
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    data[idx] *= 2.0f;
}

int main() {
    // 配置内核A偏好更多共享内存
    cudaFuncSetCacheConfig(kernelA, cudaFuncCachePreferShared);
    
    // 配置内核B偏好更多L1缓存
    cudaFuncSetCacheConfig(kernelB, cudaFuncCachePreferL1);
    
    // 启动内核
    kernelA<<<grid, block>>>(d_data);
    kernelB<<<grid, block>>>(d_data);
}

问题:

  • 这些配置对内核A和B分别有什么影响?
  • 这些配置是保证生效还是仅作为提示?
  • 如果设备资源不足,会发生什么?

4. 分析以下常量内存与全局内存性能对比的场景:

cpp 复制代码
// 场景A:使用常量内存
__constant__ float const_coeff[32];

__global__ void kernelConst(float* input, float* output, int N) {
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    if (idx < N) {
        float sum = 0.0f;
        for (int i = 0; i < 32; i++) {
            sum += input[idx] * const_coeff[i];
        }
        output[idx] = sum;
    }
}

// 场景B:使用全局内存
__global__ void kernelGlobal(float* input, float* output, float* coeff, int N) {
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    if (idx < N) {
        float sum = 0.0f;
        for (int i = 0; i < 32; i++) {
            sum += input[idx] * coeff[i];
        }
        output[idx] = sum;
    }
}

问题:

  • 在什么情况下场景A会比场景B性能更好?
  • 常量内存的缓存机制如何工作?
  • 如果系数数组大小为1024,还能使用常量内存吗?为什么?

5. 分析分布式共享内存直方图示例的数据流:

参考2.2.3.8节中的clusterHist_kernel示例。

问题:

  • 描述数据从输入到最终全局内存直方图的完整流程。
  • 解释为什么需要每个线程块有bins_per_block个桶。
  • 如果集群大小为4,nbins=1024bins_per_block应该是多少?
  • 说明两次cluster.sync()的具体作用。

五、编程练习题

题目1:常量内存查找表优化

编写一个CUDA程序,使用常量内存实现一个查找表(LUT)对图像进行颜色变换。

要求:

  1. 创建一个大小为256的查找表,将输入灰度值映射到输出灰度值(例如:伽马校正、反色、对数变换等)
  2. 将查找表存储在常量内存中
  3. 实现一个内核,对输入图像应用查找表变换
  4. 对比使用常量内存和全局内存存储查找表的性能差异

代码框架:

cpp 复制代码
#include <cuda_runtime.h>
#include <stdio.h>
#include <stdlib.h>

// 常量内存查找表
__constant__ unsigned char const_lut[256];

// 全局内存查找表(将由cudaMalloc分配)

// 使用常量内存的内核
__global__ void applyLUTConst(unsigned char* input, unsigned char* output, int size) {
    // TODO: 实现使用常量内存查找表的图像变换
    // 每个线程处理一个像素
}

// 使用全局内存的内核
__global__ void applyLUTGlobal(unsigned char* input, unsigned char* output, 
                               unsigned char* lut, int size) {
    // TODO: 实现使用全局内存查找表的图像变换
}

// 生成查找表(示例:反色变换)
void generateLUT(unsigned char* lut) {
    for (int i = 0; i < 256; i++) {
        lut[i] = 255 - i;  // 反色
    }
}

// 验证结果
bool verifyResult(unsigned char* h_input, unsigned char* h_output, 
                  unsigned char* lut, int size) {
    for (int i = 0; i < size; i++) {
        if (h_output[i] != lut[h_input[i]]) {
            printf("Mismatch at %d: %d vs %d\n", i, h_output[i], lut[h_input[i]]);
            return false;
        }
    }
    return true;
}

int main() {
    int width = 4096;
    int height = 4096;
    int size = width * height;
    
    // 分配主机内存
    unsigned char* h_input = (unsigned char*)malloc(size);
    unsigned char* h_output = (unsigned char*)malloc(size);
    
    // 初始化输入图像(随机数据)
    for (int i = 0; i < size; i++) {
        h_input[i] = rand() % 256;
    }
    
    // 生成查找表
    unsigned char lut[256];
    generateLUT(lut);
    
    // TODO: 分配设备内存
    unsigned char *d_input, *d_output_const, *d_output_global, *d_lut;
    
    // TODO: 拷贝输入数据到设备
    
    // TODO: 将查找表拷贝到常量内存和全局内存
    
    // TODO: 配置内核启动参数
    
    // TODO: 启动两个内核并测量时间
    
    // TODO: 验证结果
    
    // TODO: 释放内存
    
    return 0;
}

扩展要求:

  • 使用cudaEvent_t测量两个内核的执行时间
  • 尝试不同的查找表大小(256, 512, 1024)并观察常量内存限制
  • 实现更复杂的变换函数(如伽马校正、S曲线等)

题目2:分布式共享内存矩阵分块求和

编写一个CUDA程序,使用分布式共享内存实现大矩阵的分块求和。矩阵被分成多个块,每个线程块处理一个子矩阵,然后使用分布式共享内存在集群内汇总部分和。

要求:

  1. 将矩阵分成多个块,每个线程块处理一个块
  2. 每个线程块计算其子矩阵的元素和,存储在共享内存中
  3. 使用分布式共享内存,让集群内的所有线程块能够访问彼此的部分和
  4. 集群内的一个线程块(如块0)汇总所有部分和,得到集群的总和
  5. 最后将所有集群的总和使用全局内存原子操作汇总

代码框架:

cpp 复制代码
#include <cuda_runtime.h>
#include <cooperative_groups.h>
#include <stdio.h>

// 使用分布式共享内存的矩阵分块求和内核
__global__ void clusterMatrixSum(float* matrix, float* result, 
                                 int rows, int cols, 
                                 int block_rows, int block_cols) {
    extern __shared__ float smem[];
    namespace cg = cooperative_groups;
    
    // TODO: 获取集群信息
    // - 集群组
    // - 集群大小
    // - 当前块在集群中的秩
    
    // TODO: 每个线程块计算其子矩阵的和
    // 1. 确定当前块处理的矩阵区域
    // 2. 每个线程累加其负责的元素
    // 3. 使用共享内存进行块内归约
    
    // TODO: 集群同步,确保所有块完成计算
    
    // TODO: 使用分布式共享内存共享部分和
    // - 将每个块的部分和写入共享内存
    // - 让块0访问所有块的部分和
    
    // TODO: 块0计算集群总和
    
    // TODO: 集群同步,确保分布式操作完成
    
    // TODO: 将集群总和写回全局内存
}

int main() {
    int rows = 4096;
    int cols = 4096;
    size_t matrix_size = rows * cols * sizeof(float);
    
    // 分配主机内存
    float* h_matrix = (float*)malloc(matrix_size);
    float h_result = 0.0f;
    
    // 初始化矩阵(全1矩阵,方便验证)
    for (int i = 0; i < rows * cols; i++) {
        h_matrix[i] = 1.0f;
        h_result += 1.0f;
    }
    
    // 分配设备内存
    float *d_matrix, *d_result;
    cudaMalloc(&d_matrix, matrix_size);
    cudaMalloc(&d_result, sizeof(float));
    
    cudaMemcpy(d_matrix, h_matrix, matrix_size, cudaMemcpyHostToDevice);
    cudaMemset(d_result, 0, sizeof(float));
    
    // TODO: 配置内核启动参数
    // - 选择线程块大小(如16x16)
    // - 根据集群大小和矩阵大小计算网格维度
    // - 根据直方图大小决定集群大小
    // - 设置动态共享内存大小
    // - 使用cudaLaunchKernelEx设置集群维度
    
    printf("CPU result: %f\n", h_result);
    
    // 拷贝结果回主机
    float gpu_result;
    cudaMemcpy(&gpu_result, d_result, sizeof(float), cudaMemcpyDeviceToHost);
    printf("GPU result: %f\n", gpu_result);
    printf("Difference: %f\n", h_result - gpu_result);
    
    // 释放内存
    cudaFree(d_matrix);
    cudaFree(d_result);
    free(h_matrix);
    
    return 0;
}

进阶挑战:

  • 处理矩阵大小不是块大小整数倍的情况
  • 实现多级归约:块内归约 → 集群内归约 → 全局归约
  • 比较使用分布式共享内存和不使用的性能差异

参考答案概要

选择题答案

  1. C
  2. B
  3. B
  4. B
  5. C
  6. C
  7. C
  8. B
  9. B
  10. C
  11. B
  12. B
  13. B
  14. C
  15. B

填空题答案

  1. __constant__,网格,应用程序
  2. totalConstMem
  3. cudaMemcpyToSymbol()
  4. 设备,所有SM
  5. 共享内存,SM
  6. 忽略
  7. 9.0,线程块集群
  8. 集群大小,每线程块共享内存大小
  9. cluster.sync()
  10. cluster.map_shared_rank()
  11. 普通共享内存
  12. 小规模、只读、全局访问
  13. cudaFuncSetCacheConfig()
  14. cudaLaunchAttributeClusterDimension
  15. 线程块退出

简答题、分析题和编程题答案要点(部分示例)

分析题1答案要点:

  • 错误:cudaMemcpy(coefficients, h_coeff, ...) 应使用 cudaMemcpyToSymbol
  • 性能问题:每个线程循环256次,计算量大,且常量内存广播优势未充分利用
  • 改进:使用cudaMemcpyToSymbol,考虑向量化或减少循环次数

分析题2答案要点:

  • 缺少cluster.sync():初始化后需要同步确保所有块准备就绪
  • 缺少cluster.sync():分布式更新后需要同步确保操作完成
  • 数据一致性问题:原子操作可能不足以保证顺序

编程题1核心实现:

cpp 复制代码
// 常量内存内核
__global__ void applyLUTConst(unsigned char* input, unsigned char* output, int size) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < size) {
        output[idx] = const_lut[input[idx]];
    }
}

// 拷贝查找表到常量内存
cudaMemcpyToSymbol(const_lut, lut, sizeof(lut));

编程题2核心概念:

  • 使用cg::this_cluster()获取集群组
  • 使用cluster.sync()进行集群同步
  • 使用cluster.map_shared_rank()访问远程共享内存
  • 动态集群大小配置使用cudaLaunchAttributeClusterDimension
相关推荐
大傻^2 小时前
LangChain4j 记忆架构:ChatMemory、持久化与跨会话状态
java·人工智能·windows·架构·langchain4j
生活予甜2 小时前
广柔扁平电缆在机器人AI技术创新应用中的前景探索
人工智能·机器人
找藉口是失败者的习惯2 小时前
从LLM到Agent:大语言模型核心概念指南
人工智能·语言模型·自然语言处理
接着奏乐接着舞。2 小时前
5分钟本地跑起大模型
人工智能·llama
liliangcsdn2 小时前
OpenAI流模式下思考过程的获取示例
人工智能
workflower2 小时前
大型语言模型简史
人工智能·语言模型·自然语言处理·chatgpt·机器人·集成测试·ai编程
superkcl20222 小时前
C++初始化 和 赋值
开发语言·c++·算法
發糞塗牆2 小时前
【Azure 架构师学习笔记 】- Azure AI(20) - Azure Agent实战落地
人工智能·ai·azure
HIT_Weston2 小时前
16、【Agent】【OpenCode】源码构建(Bun介绍)
人工智能·agent·opencode