CUDA编程指南阅读笔记(六)

本文详细阐述了CUDA环境下矩阵乘法的优化策略,特别是如何利用共享内存提高性能。通过分治策略和共享内存的有效利用,显著减少了全局内存访问次数,从而提升了计算效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

4. CUDA C语言编程接口

        接上文。

4.3 CUDA C Runtime

4.3.3 共享内存(Shared Memory)

        共享内存是CUDA设备中非常重要的一个存储区域,有效地使用共享内存可以充分利用CUDA设备的潜能,极大提升程序性能。那么,共享内存有哪些特点呢?
        1、共享内存(shared Memory)是集成在GPU处理器芯片上的(on-chip),因此相比于存在于显存颗粒中的全局内存(global Memory)和本地内存(local Memory),它具有更高的传输带宽,一般情况下,共享内存的带宽大约是全局内存带宽的7-10倍。
        2、共享内存的容量很小。根据NVIDIA官方文档的说法,在计算能力1.x的设备中,每一个流多处理器(Streaming Multiprocessor)上的共享内存容量为16KB。对于计算能力2.x、3.0及3.5的设备该参数为48KB。因此共享内存是稀有资源。
        3、共享内存在物理上被划分为很多块,每一块被称为一个存储体(bank)。在同一时刻,CUDA设备可以同时访问多个存储体。因此,如果一次针对共享内存的访存操作需要读取n个地址,而这n个地址恰好分布在n个不同的存储体(bank)中,那么只需要一个存取周期就可以完成n个地址的访存任务了。对于计算能力1.x的设备,共享内存被平均划分为16个存储体。而对于计算能力2.x、3.0及3.5的设备此参数为32。在共享内存中,相邻两块32bit的数据分别属于相邻的两个存储体。存储体每两个时钟周期可以传输32位数据。
        4、共享内存既可以静态分配,也可以动态分配。
        从共享内存的这些特点中我们可以看出,它实际上相当于一个程序员可以操控的缓存(cache),下面,我们使用矩阵乘法的例子来说明如何有效使用共享内存。
        首先,我们使用最直观的方法来完成矩阵乘法C = A x B:读取A的每一行和B的每一列,顺次完成计算任务。矩阵乘法的示意图如下所示:


下面是矩阵乘法的CUDA C主要实现代码:
[cpp]  view plain copy
  1. // Matrices are stored in row-major order:  
  2. // M(row, col) = *(M.elements + row * M.width + col)  
  3. typedef struct {  
  4.     int width;  
  5.     int height;  
  6.     float *elements;  
  7. } Matrix;  
  8.   
  9. // Thread block size  
  10. #define BLOCK_SIZE 16  
  11.   
  12. // Forward declaration of the matrix multiplication kernel  
  13. __global__ void MatMulKernel(const Matrix, const Matrix, Matrix);  
  14.   
  15. // Matrix multiplication - Host code  
  16. // Matrix dimensions are assumed to be multiples of BLOCK_SIZE  
  17. void MatMul(const Matrix A, const Matrix B, Matrix C) {  
  18.     // Load A and B to device memory  
  19.     Matrix d_A;  
  20.     d_A.width = A.width; d_A.height = A.height;  
  21.     size_t size = A.width * A.height * sizeof(float);  
  22.     cudaMalloc(&d_A.elements, size);  
  23.     cudaMemcpy(d_A.elements, A.elements, size, cudaMemcpyHostToDevice);  
  24.     Matrix d_B;  
  25.     d_B.width = B.width; d_B.height = B.height;  
  26.     size = B.width * B.height * sizeof(float);  
  27.     cudaMalloc(&d_B.elements, size);  
  28.     cudaMemcpy(d_B.elements, B.elements, size, cudaMemcpyHostToDevice);  
  29.   
  30.     // Allocate C in device memory  
  31.     Matrix d_C;  
  32.     d_C.width = C.width; d_C.height = C.height;  
  33.     size = C.width * C.height * sizeof(float);  
  34.     cudaMalloc(&d_C.elements, size);  
  35.   
  36.     // Invoke kernel  
  37.     dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);  
  38.     dim3 dimGrid(B.width / dimBlock.x, A.height / dimBlock.y);  
  39.     MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C);  
  40.   
  41.     // Read C from device memory  
  42.     cudaMemcpy(C.elements, d_c.elements, size, cudaMemcpyDeviceToHost);  
  43.   
  44.     // Free device memory  
  45.     cudaFree(d_A.elements);  
  46.     cudaFree(d_B.elements);  
  47.     cudaFree(d_C.elements);  
  48. }  
  49.   
  50. // Matrix multiplication kernel called by MatMul()  
  51. __global__ void MatMulKernel(Matrix A, Matrix B, Matrix C) {  
  52.     // Each thread computes one element of C  
  53.     // by accumulating results into Cvalue  
  54.     float Cvalue = 0;  
  55.     int row  = blockIdx.y * blockDim.y + threadIdx.y;  
  56.     int col = blockIdx.x * blockDim.x + threadIdx.xl  
  57.     for (int e = 0; e < A.width; ++e)  
  58.         Cvalue += A.elements[row * A.width + e] * B.elements[e * B.width + col];  
  59.     C.elements[row * C.width + col] = Cvalue;  
  60. }  
可以看出,为了计算矩阵C的任何一个元素,程序都需要从全局内存(global memory)中获得矩阵A的一行和矩阵B的一列。因此,完成这一计算矩阵A被读取了B.width次,矩阵B被读取了A.height次。
        现在我们来使用共享内存(shared memory)实现矩阵乘法。假设矩阵C可以被划分为若干个较小的子方阵C sub,我们使用一个线程块(thread block)来负责某一子方阵的计算,线程块中的每一个线程(thread)正好负责子方阵C sub中一个元素的计算。这样划分后,任何一个结果子方阵C sub'(尺寸为block_size * block_size)都是与该方阵具有相同行索引的尺寸为A.width * block_size的A的子矩阵A sub和与该方阵具有相同列索引的尺寸为block_size * B.height的B的子矩阵B sub相乘所得到。
        为了匹配设备的计算资源,两个子矩阵Asub和Bsub被划分为尽可能多的分离的维度为block_size的子方阵,Csub的值便是这些子矩阵相乘后相加所得到的结果。子矩阵乘法的执行顺序都是首先将它们从全局内存(global memory)拷贝到共享内存(shared memory)(线程块中的每一个线程正好负责方阵一个元素的拷贝),然后由线程自己完成相应元素的计算任务,利用寄存器存储局部结果,最后将寄存器的内容与新得到的计算结果依此累加起来得到最终运算结果并将其传输到全局内存(global memory)中。
        通过使用这种分治的计算策略,共享内存得到了很好的利用,采用这种方案计算完成时全局内存中矩阵A被访问的次数为B.width / block_size,矩阵B被访问的次数为A.height / block_size,很明显,这为我们节省了非常多的全局内存带宽。优化后的矩阵计算示意图如下所示:

        为了提升计算效率,我们为类型Matrix增加了一个成员变量stride。__device__函数用来获得和设置子矩阵的元素。下面是优化后的代码:
[cpp]  view plain copy
  1. // Matrices are stored in row-major order;  
  2. // M(row, col) = *(M.elements + row * M.stride + col)  
  3. typedef struct {  
  4.     int width;  
  5.     int height;  
  6.     int stride;  
  7.     float* elements;  
  8. } Matrix;  
  9.   
  10. // Get a matrix element  
  11. __device__ float GetElement(const Matrix A, int row, int col) {  
  12.     return A.elements[row * A.stride + col];  
  13. }  
  14.   
  15. // Set a matrix element  
  16. __device__ void SetElement(Matrix A, int row, int col, float value) {  
  17.     A.elements[row * A.stride + col] = value;  
  18. }  
  19.   
  20. // Get the BLOCK_SIZExBLOCK_SIZE sub-matrix Asub of A that is  
  21. // located col sub-matrices to the right and row sub-matrices down  
  22. // from the upper-left corner of A  
  23. __device__ Matrix GetSubMatrix(Matrix A, int row, int col) {  
  24.     Matrix Asub;  
  25.     Asub.width = BLOCK_SIZE;  
  26.     Asub.height = BLOCK_SIZE;  
  27.     Asub.stride = A.stride;  
  28.     Asub.elements = &A.elements[A.stride * BLOCK_SIZE * row + BLOCK_SIZE * col];  
  29.     return Asub;  
  30. }  
  31.   
  32. // Thread block size  
  33. #define BLOCK_SIZE 16  
  34.   
  35. // Forward declaration of the matrix multiplication kernel  
  36. __global__ void MatMulKernel(const Matrix, const Matrix, Matrix);  
  37.   
  38. // Matrix multiplication - Host code  
  39. // Matrix dimensions are assumed to be multiples of BLOCK_SIZE  
  40. void MatMul(const Matrix A, const Matrix B, Matrix C) {  
  41.     // Load A and B to device memory  
  42.     Matrix d_A;  
  43.     d_A.width = d_A.stride = A.width;  
  44.     d_A.height = A.height;  
  45.     size_t size = A.width * A.height * sizeof(float);  
  46.     cudaMalloc(&d_A.elements, size);  
  47.     cudaMemcpy(d_A.elements, A.elements, size, cudaMemcpyHostToDevice);  
  48.     Matrix d_B;  
  49.     d_B.width = d_B.stride = B.width;  
  50.     d_B.height = B.height;  
  51.     size = B.width * B.height * sizeof(float);  
  52.     cudaMalloc(&d_B.elements, size);  
  53.     cudaMemcpy(d_B.elements, B.elements, size, cudaMemcpyHostToDevice);  
  54.   
  55.     // Allocate C in device memory  
  56.     Matrix d_C;  
  57.     d_C.width = d_C.stride = C.width;  
  58.     d_C.height = C.height;  
  59.     size = C.width * C.height * sizeof(float);  
  60.     cudaMalloc(&d_C.elements, size);  
  61.   
  62.     // Invoke kernel  
  63.     dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);  
  64.     dim3 dimGrid(B.width / dimBlock.x, A.height / dimBlock.y);  
  65.     MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C);  
  66.   
  67.     // Read C from device memory  
  68.     cudaMemcpy(C.elements, d_C.elements, size, cudaMemcpyDeviceToHost);  
  69.   
  70.     // Free device memory  
  71.     cudaFree(d_A.elements);  
  72.     cudaFree(d_B.elements);  
  73.     cudaFree(d_C.elements);  
  74. }  
  75.   
  76. // Matrix multiplication kernel called by MatMul()  
  77. __global__ void MatMulKernel(Matrix A, Matrix B, Matrix C) {  
  78.     // Block row and column  
  79.     int blockRow = blockIdx.y;  
  80.     int blockCol = blockIdx.x;  
  81.   
  82.     // Each thread block computes one sub-matrix Csub of C  
  83.     Matrix Csub = GetSubMatrix(C, blockRow, blockCol);  
  84.   
  85.     // Each thread computes one element of Csub  
  86.     // by accumulating results into Cvalue  
  87.     float Cvalue = 0;  
  88.   
  89.     // Thread row and column within Csub  
  90.     int row = threadIdx.y;  
  91.     int col = threadIdx.x;  
  92.   
  93.     // Look over all the sub-matrices of A and B that are required to compute Csub  
  94.     // Multiply each pair of sub-matrices together and accumulate the results  
  95.     for (int m = 0; m < (A.width / BLOCK_SIZE); ++m) {  
  96.         // Get sub-matrix Asub of A  
  97.         Matrix Asub = GetSubMatrix(A, blockRow, m);  
  98.           
  99.         // Get sub-matrix Bsub of B  
  100.         Matrix Bsub = GetSubMatrix(B, m, blockCol);  
  101.   
  102.         // Shared memory used to store Asub and Bsub respectively  
  103.         __shared__ float As[BLOCK_SIZE][BLOCK_SIZE];  
  104.         __shared__ float Bs[BLOCK_SIZE][BLOCK_SIZE];  
  105.   
  106.         // Load Asub and Bsub from device memory to shared memory  
  107.         // Each thread loads one element of each sub-matrix  
  108.         As[row][col] = GetElement(Asub, row, col);  
  109.         Bs[row][col] = GetElement(Bsub, row, col);  
  110.   
  111.         // Synchronize to make sure the sub-matrices are loaded  
  112.         // before starting the computation  
  113.         __syncthreads();  
  114.   
  115.         // Multiply Asub and Bsub together  
  116.         for (int e = 0; e < BLOCK_SIZE; ++e)  
  117.             Cvalue += As[row][e] * Bs[e][col];  
  118.   
  119.         // Synchronize to make sure that the preceding computation is done before  
  120.         // loading two new sub-matrices of A and B in the next iteration  
  121.         __syncthreads();  
  122.     }  
  123.   
  124.     // Write Csub to device memory  
  125.     // Each thread writes one element  
  126.     SetElement(Csub, row, col, Cvalue);  
  127. }

同一个block中的线程在95行的for循环中获取到的Asub,Bsub,Csub是一样的,每个线程就负责Csub内元素的计算

http://blog.youkuaiyun.com/csgxy123/article/details/10018531

第一章导论 1 1.1 从图形处理到通用并行计算 1 1.2 CUDATM:一种通用并行计算架构 3 1.3 一种可扩展的编程模型 3 1.4 文档结构 4 第二章编程模型 7 2.1 内核 7 2.2 线程层次 8 2.3 存储器层次 11 2.4 异构编程 11 2.5 计算能力 11 第三章编程接口 15 3.1 用nvcc编译 15 3.1.1 编译流程 16 3.1.1.1 离线编译 16 3.1.1.2 即时编译 16 3.1.2 二进制兼容性 17 3.1.3 PTX兼容性 17 3.1.4 应用兼容性 18 3.1.5 C/C++兼容性 19 3.1.6 64位兼容性 19 3.2 CUDA C运行时 3.2.1 初始化 20 3.2.2 设备存储器 20 3.2.3 共享存储器 24 3.2.4 分页锁定主机存储器 32 3.2.4.1 可分享存储器(portable memory) 34 3.2.4.2 写结合存储器 34 3.2.4.3 被映射存储器 34 3.2.5 异步并发执行 35 3.2.5.1 主机和设备间异步执行 35 3.2.5.2 数据传输和内核执行重叠 36 3.2.5.3 并发内核执行 36 3.2.5.4 并发数据传输 36 3.2.5.5 流 37 3.2.5.6 事件 41 3.2.5.7 同步调用 42 3.2.6 多设备系统 42 3.2.6.1 枚举设备 42 3.2.6.2 设备指定 42 3.2.6.3 流和事件行为 43 3.2.6.4 p2p存储器访问 44 3.2.6.5 p2p存储器复制 45 3.2.6.6 统一虚拟地址空间 45 3.2.6.7 错误检查 46 3.2.7 调用栈 47 3.2.8 纹理和表面存储器 47 3.2.8.1 纹理存储器 47 3.2.8.2 表面存储器(surface) 60 3.2.8.3 CUDA 数组 65 目录iii 3.2.8.4 读写一致性 66 3.2.9 图形学互操作性 66 3.2.9.1 OpenGL互操作性 67 3.2.9.2 Direct3D互操作性 70 3.2.9.3 SLI(速力)互操作性 82 3.3 版本和兼容性 82 3.4 计算模式 83 3.5 模式切换 84 3.6 Windows上的Tesla计算集群模式 85 第四章硬件实现 87 4.1 SIMT 架构 87 4.2 硬件多线程 88 第五章性能指南 91 5.1 总体性能优化策略 91 5.2 最大化利用率 91 5.2.1 应用层次 91 5.2.2 设备层次 92 5.2.3 多处理器层次 92 5.3 最大化存储器吞吐量 94 5.3.1 主机和设备的数据传输 95 5.3.2 设备存储器访问 96 5.3.2.1 全局存储器 96 5.3.2.2 本地存储器 98 5.3.2.3 共享存储器 99 5.3.2.4 常量存储器 100 5.3.2.5 纹理和表面存储器 100 5.4 最大化指令吞吐量 100 iv CUDA编程指南5.0中文版 5.4.1 算术指令 101 5.4.2 控制流指令 104 5.4.3 同步指令 105 附录A 支持CUDA的GPU 107 附录B C语言扩展 109 B.1 函数类型限定符 109 B.1.1 device 109 B.1.2 global 109 B.1.3 host 109 B.1.4 noinline 和forceinline 110 B.2 变量类型限定符 110 B.2.1 device 111 B.2.2 constant 111 B.2.3 shared 112 B.2.4 restrict 113 B.3 内置变量类型 115 B.3.1 char1、uchar1、char2、uchar2、char3、uchar3、char4、 uchar4、short1、ushort1、short2、ushort2、short3、ushort3、 short4、ushort4、int1、uint1、int2、uint2、int3、uint3、 int4、uint4、long1、ulong1、long2、ulong2、long3、ulong3、 long4、ulong4、float1、float2、float3、float4、double2 115 B.3.2 dim3类型 115 B.4 内置变量 115 B.4.1 gridDim 115 B.4.2 blockIdx 115 B.4.3 blockDim 117 B.4.4 threadIdx 117 B.4.5 warpSize 117 目录v B.5 存储器栅栏函数 117 B.6 同步函数 119 B.7 数学函数 120 B.8 纹理函数 120 B.8.1 纹理对象函数 120 B.8.1.1 tex1Dfetch() 120 B.8.1.2 tex1D() 121 B.8.1.3 tex2D() 121 B.8.1.4 tex3D() 121 B.8.1.5 tex1DLayered() 121 B.8.1.6 tex2DLayered() 122 B.8.1.7 texCubemap() 122 B.8.1.8 texCubemapLayered() 122 B.8.1.9 tex2Dgather() 123 B.8.2 纹理参考函数 123 B.8.2.1 tex1Dfetch() 123 B.8.2.2 tex1D() 124 B.8.2.3 tex2D() 124 B.8.2.4 tex3D() 125 B.8.2.5 tex1DLayered() 125 B.8.2.6 tex2DLayered() 125 B.8.2.7 texCubemap() 125 B.8.2.8 texCubemapLayered() 126 B.8.2.9 tex2Dgather() 126 B.9 表面函数(surface) 126 B.9.1 表面对象函数 127 B.9.1.1 surf1Dread() 127 B.9.1.2 surf1Dwrite() 127 vi CUDA编程指南5.0中文版 B.9.1.3 surf2Dread() 127 B.9.1.4 surf2Dwrite() 128 B.9.1.5 surf3Dread() 128 B.9.1.6 surf3Dwrite() 128 B.9.1.7 surf1DLayeredread() 129 B.9.1.8 surf1DLayeredwrite() 129 B.9.1.9 surf2DLayeredread() 129 B.9.1.10 surf2DLayeredwrite() 130 B.9.1.11 surfCubemapread() 130 B.9.1.12 surfCubemapwrite() 131 B.9.1.13 surfCubemapLayeredread() 131 B.9.1.14 surfCubemapLayeredwrite() 131 B.9.2 表面引用API 132 B.9.2.1 surf1Dread() 132 B.9.2.2 surf1Dwrite() 132 B.9.2.3 surf2Dread() 132 B.9.2.4 surf2Dwrite() 133 B.9.2.5 surf3Dread() 133 B.9.2.6 surf3Dwrite() 133 B.9.2.7 surf1DLayeredread() 134 B.9.2.8 surf1DLayeredwrite() 134 B.9.2.9 surf2DLayeredread() 135 B.9.2.10 surf2DLayeredwrite() 135 B.9.2.11 surfCubemapread() 135 B.9.2.12 surfCubemapwrite() 136 B.9.2.13 surfCubemapLayeredread() 136 B.9.2.14 surfCubemapLayeredwrite() 137 B.10 时间函数 137 目录vii B.11 原子函数 137 B.11.1 数学函数 138 B.11.1.1 atomicAdd() 138 B.11.1.2 atomicSub() 139 B.11.1.3 atomicExch() 139 B.11.1.4 atomicMin() 140 B.11.1.5 atomicMax() 140 B.11.1.6 atomicInc() 140 B.11.1.7 atomicDec() 141 B.11.1.8 atomicCAS() 141 B.11.2 位逻辑函数 141 B.11.2.1 atomicAnd() 141 B.11.2.2 atomicOr() 142 B.11.2.3 atomicXor() 142 B.12 束表决(warp vote)函数 142 B.13 束洗牌函数 143 B.13.1 概览 143 B.13.2 在束内广播一个值 144 B.13.3 计算8个线程的前缀和 145 B.13.4 束内求和 146 B.14 取样计数器函数 146 B.15 断言 147 B.16 格式化输出 148 B.16.1 格式化符号 149 B.16.2 限制 149 B.16.3 相关的主机端API 150 B.16.4 例程 151 B.17 动态全局存储器分配 152 viii CUDA编程指南5.0中文版 B.17.1 堆存储器分配 153 B.17.2 与设备存储器API的互操作 154 B.17.3 例程 154 B.17.3.1 每个线程的分配 154 B.17.3.2 每个线程块的分配 155 B.17.3.3 在内核启动之间持久的分配 156 B.18 执行配置 159 B.19 启动绑定 160 B.20 #pragma unroll 162 B.21 SIMD 视频指令 163 附录C 数学函数 165 C.1 标准函数 165 C.1.1 单精度浮点函数 165 C.1.2 双精度浮点函数 168 C.2 内置函数 171 C.2.1 单精度浮点函数 172 C.2.2 双精度浮点函数 172 附录D C++语言支持 175 D.1 代码例子 175 D.1.1 数据类 175 D.1.2 派生类 176 D.1.3 类模板 177 D.1.4 函数模板 178 D.1.5 函子类 178 D.2 限制 180 D.2.1 预处理符号 180 D.2.2 限定符 180 目录ix D.2.2.1 设备存储器限定符 180 D.2.2.2 Volatile限定符 182 D.2.3 指针 182 D.2.4 运算符 183 D.2.4.1 赋值运算符 183 D.2.4.2 地址运算符 183 D.2.5 函数 183 D.2.5.1 编译器生成的函数 183 D.2.5.2 函数参数 184 D.2.5.3 函数内静态变量 184 D.2.5.4 函数指针 184 D.2.5.5 函数递归 185 D.2.6 类 185 D.2.6.1 数据成员 185 D.2.6.2 函数成员 185 D.2.6.3 虚函数 185 D.2.6.4 虚基类 185 D.2.6.5 Windows相关 185 D.2.7 模板 186 附录E 纹理获取 187 E.1 最近点取样 187 E.2 线性滤波 187 E.3 查找表 189 附录F 计算能力 191 F.1 特性和技术规范 191 F.2 浮点标准 195 F.3 计算能力1.x 198 x CUDA编程指南5.0中文版 F.3.1 架构 198 F.3.2 全局存储器 199 F.3.2.1 计算能力1.0和1.1的设备 199 F.3.2.2 计算能力1.2和1.3的设备 199 F.3.3 共享存储器 201 F.3.3.1 32位步长访问 201 F.3.3.2 32位广播访问 202 F.3.3.3 8位和16位访问 205 F.3.3.4 大于32位访问 205 F.4 计算能力2.x 206 F.4.1 架构 206 F.4.2 全局存储器 208 F.4.3 共享存储器 209 F.4.3.1 32位步长访问 209 F.4.3.2 大于32位访问 210 F.4.4 常量存储器 211 F.5 计算能力3.x 211 F.5.1 架构 211 F.5.2 全局存储器访问 212 F.5.3 共享存储器 213 F.5.3.1 64位模式 213 F.5.3.2 32位模式 213 附录G 驱动API 215 G.1 上下文 218 G.2 模块 219 G.3 内核执行 220 G.4 运行时API和驱动API的互操作性 222 G.5 注意 223
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值