1 全局内存
GPU的全局内存之所以是全局的,主要是因为GPU与CPU都可以对它进行写操作。任何设备都可以通过PCI-E总线对其进行访问。GPU之间不通过CPU,直接将数据从一块GPU卡上的数据传输到另一个GPU卡上。
CPU主机端处理器可以通过以下三种方式对GPU上的内存进行访问:
【1】显式地阻塞传输
【2】显式地非阻塞传输
【3】隐式的使用零内存复制。
2 共享内存
什么是共享内存:实际上可受用户控制的一级缓存。大小:每个SM中的一级缓存与共享内存共享一个64KB的内存段:
什么时候使用共享内存:只有当数据重复利用,全局内存合并,或者线程之间有共享的数据使用
编写代码时候:shared 的共享内存的声明,让一个线程块block 上的多个线程进行通信和协作。
那么这些多线程之间的【通信与协作】还需要其他操作:
【原因】: 如果线程A将一个值写入到共享内存,并且我们希望线程B对这个值进行一些操作,那么只有当线程A的写入操作完成之后,线程B才能开始执行它的操作。
【__syncthread()】完成线程同步【__syncthread()】完成线程同步需要注意的部分:当某些线程需要执行一条指令,而其他线程不需要执行时。
【共享内存的用例】:
【矩阵乘法】CPU实现 a*b = c
的矩阵乘法(矩阵尺寸是n*m的,n和m大于1000)
【优化思路】1. 将矩阵分块进行计算
2. 使用share memory进行优化
3. 将数据绑定在texture上
【CPU实现的矩阵乘法】
/*
* CPUMatMultiply:CPU下矩阵乘法
* a:第一个矩阵指针,表示a[M][N]
* b:第二个矩阵指针,表示b[N][S]
* result:结果矩阵,表示为result[M][S]
*/
void CUPMatMultiply(const int *a, const int *b, int *result, const int M, const int N,const int S)
{
for(int i = 0; i
【CUDA】的实现
从CPU上直接移植矩阵乘法到GPU上是非常简单的,不需要for循环,直接通过CUDA线程的id号,即threadIdx.x
和threadIdx.y
即可操作相应的数据。
/* gpuMatMultKernel:GPU下矩阵乘法核函数
* a:第一个矩阵指针,表示a[M][N]
* b:第二个矩阵指针,表示b[N][S]
* result:结果矩阵,表示result[M][S]
---------------------
作者:hackairM
来源:CSDN
原文:https://blog.csdn.net/u010335328/article/details/52304688
版权声明:本文为博主原创文章,转载请附上博文链接!
*/
__global__ void gpuMatMultKernel(const int *a, const int *b, int *result, const int M, const int N, const int S)
{
// 首先是grid, block and thread 的id 关系来的到每个线程的id
int threadId = (blockIdx.y * blockDim.y + threadIdx.y) * gridDim.x * blockDim.x
+ blockIdx.x * blockDim.x + threadIdx.x;
if (threadId < M * S)
{
int row = threadId / S;
int column = threadId % S;
result[threadId] = 0;
for (int i = 0; i < N; i++)
{
result[threadId] += a[row * N + i] * b[i * S + column];
}
}
}
【共性内存分块运算】
CUDA 支持共享内存: 关键字__shared__添加到声明中,这使这个变量驻留在共享内存中。
【共享内存延迟低】共享内存缓存区驻留在物理GPU 上,而不是驻留在GPU之外的系统内存中。
【使用共享内存的核函数
/* gpuMatMultWithSharedKernel:GPU下使用shared内存的矩阵乘法
* a:第一个矩阵指针,表示a[height_A][width_A]
* b:第二个矩阵指针,表示b[width_A][width_B]
* result:结果矩阵,表示result[height_A][width_B]
作者:hackairM
来源:CSDN
原文:https://blog.csdn.net/u010335328/article/details/52304688
*/
template
__global__ void gpuMatMultWithSharedKernel(const int *a, const int *b, int *result, const int height_A)
{
int block_x = blockIdx.x;
int block_y = blockIdx.y;
int thread_x = threadIdx.x;
int thread_y = threadIdx.y;
if ((thread_y + block_y * blockDim.y) * width_B + block_x * blockDim.x + thread_x >= height_A * width_B)
{
return;
}
const int begin_a = block_y * blockDim.y * width_A;
const int end_a = begin_a + width_A - 1;
const int step_a = blockDim.x;
const int begin_b = block_x * blockDim.x;
const int step_b = blockDim.y * width_B;
int result_temp = 0;
for (int index_a = begin_a, int index_b = begin_b;
index_a < end_a; index_a += step_a, index_b += step_b)
{
__shared__ int SubMat_A[BLOCK_SIZE][BLOCK_SIZE];
__shared__ int SubMat_B[BLOCK_SIZE][BLOCK_SIZE];
SubMat_A[thread_y][thread_x] = a[index_a + thread_y * width_A + thread_x];
SubMat_B[thread_y][thread_x] = b[index_b + thread_y * width_B + thread_x];
__syncthreads();
for (int i = 0; i < BLOCK_SIZE; i++)
{
result_temp += SubMat_A[thread_y][i] * SubMat_B[i][thread_x];
}
__syncthreads();
}
int begin_result = block_y * blockDim.y * width_B + begin_b;
result[begin_result + thread_y * width_B + thread_x] = result_temp;
}
【纹理内存的运用】
在某个计算应用程序中,这意味着一个线程读取的位置可能与邻近线程读取的位置“非常接近”。
从数学的角度看,这四个地址并非连续的,在一般的CPU缓存模式中,这些地址将不会缓存。但由于GPU纹理缓存是专门为了加速这种访问模式而设计的,因此如果在这种情况中使用纹理内存而不是全局内存,那么将会获得性能提升。
/* gpuMatMultWithTextureKernel:GPU下使用texture内存的矩阵乘法
* result:结果矩阵,表示为result[M][S];
* M:表示为矩阵A与矩阵result的行数
* N:表示矩阵A的列数,矩阵B的行数
* S:表示矩阵B和矩阵result的列数
---------------------
作者:hackairM
来源:CSDN
原文:https://blog.csdn.net/u010335328/article/details/52304688
*/
__global__ void gpuMatMultWithTextureKernel(int * result, const int M, const int N, const int S)
{
int x = threadIdx.x + blockIdx.x * blockDim.x;
int y = threadIdx.y + blockIdx.y * blockDim.y;
int offset = x + y * blockDim.x * gridDim.x;
if (offset < M * S)
{
int a = 0, b = 0;
int temp_result = 0;
for (int i = 0; i < N; i++)
{
a = tex1Dfetch(texA, y * N + i);
b = tex1Dfetch(texB, i * S + x);
temp_result += a * b;
}
result[offset] = temp_result;
}
}
矩阵乘法计算中,运行时间上有:共享内存 < 纹理内存 ≈ 普通GPU移植 < CPU运算。
【共享内存的局限】与L1 Cache公用一块on-chip内存,用户可以调整L1 cache与共享内存的大小组合。
【bank conflict】在on-chip内存的基础上,共享内存还实现了“并行访存”:共享内存被划分为大小相等的n个部分(每个部分称为一个bank),同一时刻的n个访存请求将分别通过这n个部分进行,从而使共享内存的带宽变为n倍!
当然,n倍带宽是最佳情况,如果这n个访存请求中存在对相同bank的请求,那么相同的请求将变为串行的,即排队进行。如果n个请求均是相同bank的,那么这n个请求就要串行执行,只能获得1倍带宽。这种现象称为bank conflict(bank冲突)。
【Warp_bank】: 一个Warp的 32个线程,在不用bank 中,这样就不会冲突了。