一、存储器架构
在GPU上的代码执行被划分为流多处理器、块和线程。GPU有几个不同的存储器空间,每个存储器空间都有特定的特征和用途以及不同的速度和范围。这个存储空间按层次结构划分为不同的组块,比如全局内存、共享内存、本地内存、常量内存和纹理内存,每个组块都可以从程序中的不同点访问。
GPU有一级和二级缓存(即L1缓存和L2缓存)。常量内存则是用于存储常量和内核参数之类的只读数据。最后,存在纹理内存,这种内存可以利用各种2D和3D的访问模式。
二、全局内存
所有的块都可以对全局内存进行读写。该存储器较慢,但是可以从你的代码的任何地方进行读写。缓存可加速对全局内存的访问。所有通过cudaMalloc分配的存储器都是全局内存。
使用全局内存示例
#include <stdio.h>
#define N 5
__global__ void gpu_global_memory(int *d_a)
{
// “array”是指向设备上全局内存的指针
d_a[threadIdx.x] = threadIdx.x;
}
int main(int argc, char **argv)
{
// 定义主机数组
int h_a[N];
// 定义设备指针
int *d_a;
cudaMalloc((void **)&d_a, sizeof(int) *N);
// 现在将数据从主机内存复制到设备内存
cudaMemcpy((void *)d_a, (void *)h_a, sizeof(int) *N, cudaMemcpyHostToDevice);
// 运行kernel
gpu_global_memory << <1, N >> >(d_a);
// 将修改后的阵列复制回主机内存
cudaMemcpy((void *)h_a, (void *)d_a, sizeof(int) *N, cudaMemcpyDeviceToHost);
printf("Array in Global Memory is: \n");
//Printing result on console
for (int i = 0; i < N; i++) {
printf("At Index: %d --> %d \n", i, h_a[i]);
}
return 0;
}
三、本地内存和寄存器堆
本地内存和寄存器堆对每个线程都是唯一的。寄存器是每个线程可用的最快存储器。当内核中使用的变量在寄存器堆中装不下的时候,将会使用本地内存存储它们,这叫寄存器溢出。
使用本地内存有两种情况:一种是寄存器不够了,一种是某些情况根本就不能放在寄存器中,例如对一个局部数组的下标进行不定索引的时候。基本上可以将本地内存看成是每个线程的唯一的全局内存部分。相比寄存器堆,本地内存要慢很多。虽然本地内存通过L1缓存和L2缓存进行了缓冲,但寄存器溢出可能会影响程序性能。
使用寄存器的示例,代码中的t_local变量是每个线程局部唯一的,将被存储在寄存器堆中。用这种变量计算的时候,计算速度将是最快速的。
#include <stdio.h>
#define N 5
__global__ void gpu_local_memory(int d_in)
{
int t_local;
t_local = d_in * threadIdx.x;
printf("Value of Local variable in current thread is: %d \n", t_local);
}
int main(int argc, char **argv)
{
printf("Use of Local Memory on GPU:\n");
gpu_local_memory << <1, N>> >(5);
cudaDeviceSynchronize();
return 0;
}
四、高速缓冲寄存器
较新的GPU上,每个流多处理器都含有自己独立的L1缓存,以及GPU有L2缓存。L2缓存是被所有的GPU中的流多处理器都共有的。所有的全局内存访问和本地内存访问都使用这些缓存,因为L1缓存在流多处理器内部独有,接近线程执行所需要的硬件单位,所以它的速度非常快。
一般来说,L1缓存和共享内存共用同样的存储硬件,一共是64KB(注意:这是和计算能力有关,不一定共用相同的存储硬件,也不一定可以配置互相占用的比例,例如计算能力5.X和6.X的GPU卡就不能。同时L1缓存和共享内存在这两个计算能力上也不是共用的,但旧的计算能力和7.X GPU卡是如此),你可以配置L1缓存和共享内存分别在这64KB中的比例。所有的全局内存访问通过L2缓存进行。纹理内存和常量内存也分别有它们独立的缓存。
五、常量内存
CUDA程序员会经常用到另外一种存储器——常量内存,NVIDIA GPU卡从逻辑上对用户提供了64KB的常量内存空间,可以用来存储内核执行期间所需要的恒定数据。常量内存对一些特定情况下的小数据量的访问具有相比全局内存的额外优势。使用常量内存也一定程度上减少了对全局内存的带宽占用。
常量内存中的变量使用__constant__关键字修饰。在之前的代码中,两个浮点数constant_f,constant_g被定义成在内核执行期间不会改变的常量。需要注意的第二点是,使用__constant__(在内核外面)定义好了它们后,它们不应该再次在内核内部定义。
#include "stdio.h"
#include<iostream>
#include <cuda.h>
#include <cuda_runtime.h>
//Defining two constants
__constant__ int constant_f;
__constant__ int constant_g;
#define N 5
//Kernel function for using constant memory
__global__ void gpu_constant_memory(float *d_in, float *d_out) {
//Thread index for current kernel
int tid = threadIdx.x;
d_out[tid] = constant_f*d_in[tid] + constant_g;
}
int main(void) {
//Defining Arrays for host
float h_in[N], h_out[N];
//Defining Pointers for device
float *d_in, *d_out;
int h_f = 2;
int h_g = 20;
// allocate the memory on the cpu
cudaMalloc((void**)&d_in, N * sizeof(float));
cudaMalloc((void**)&d_out, N * sizeof(float));
//Initializing Array
for (int i = 0; i < N; i++) {
h_in[i] = i;
}
//Copy Array from host to device
cudaMemcpy(d_in, h_in, N * sizeof(float), cudaMemcpyHostToDevice);
//Copy constants to constant memory
cudaMemcpyToSymbol(constant_f, &h_f, sizeof(int),0,cudaMemcpyHostToDevice);
cudaMemcpyToSymbol(constant_g, &h_g, sizeof(int));
//Calling kernel with one block and N threads per block
gpu_constant_memory << <1, N >> >(d_in, d_out);
//Coping result back to host from device memory
cudaMemcpy(h_out, d_out, N * sizeof(float), cudaMemcpyDeviceToHost);
//Printing result on console
printf("Use of Constant memory on GPU \n");
for (int i = 0; i < N; i++) {
printf("The expression for input %f is %f\n", h_in[i], h_out[i]);
}
//Free up memory
cudaFree(d_in);
cudaFree(d_out);
return 0;
}
六、纹理内存
纹理内存是另外一种当数据的访问具有特定的模式的时候能够加速程序执行,并减少显存带宽的只读存储器。像常量内存一样,它也在芯片内部被cache缓冲。该存储器最初是为了图形绘制而设计的,但也可以被用于通用计算。当程序进行具有很大程度上的空间邻近性的访存的时候,这种存储器变得非常高效。空间邻近性的意思是,每个线程的读取位置都和其他线程的读取位置邻近。这对那些需要处理4个邻近的相关点或者8个邻近的点的图像处理应用非常有用。
纹理引用是通过texture<>类型的变量进行定义的。定义的时候,它具有3个参数:第一个是texture<>类型的变量定义时候的参数,用来说明纹理元素的类型。在本例中,是float类型;第二个参数说明了纹理引用的类型,可以是1D的,2D的,3D的。在本例中,是1D的纹理引用;第三个参数则是读取模式,这是一个可选参数,用来说明是否要执行读取时候的自动类型转换。请一定要确保纹理引用被定义成全局静态变量,同时还要确保它不能作为参数传递给任何其他函数。在这个内核函数中,每个线程通过纹理引用读取自己线程ID作为索引位置的数据,然后复制到d_out指针指向的全局内存中。
//Matrix multiplication using shared and non shared kernal
#include "stdio.h"
#include<iostream>
#include <cuda.h>
#include <cuda_runtime.h>
#include <math.h>
#define TILE_SIZE 2
//Matrix multiplication using non shared kernel
__global__ void gpu_Matrix_Mul_nonshared(float *d_a, float *d_b, float *d_c, const int size)
{
int row, col;
col = TILE_SIZE * blockIdx.x + threadIdx.x;
row = TILE_SIZE * blockIdx.y + threadIdx.y;
for (int k = 0; k< size; k++)
{
d_c[row*size + col] += d_a[row * size + k] * d_b[k * size + col];
}
}
// Matrix multiplication using shared kernel
__global__ void gpu_Matrix_Mul_shared(float *d_a, float *d_b, float *d_c, const int size)
{
int row, col;
//Defining Shared Memory
__shared__ float shared_a[TILE_SIZE][TILE_SIZE];
__shared__ float shared_b[TILE_SIZE][TILE_SIZE];
col = TILE_SIZE * blockIdx.x + threadIdx.x;
row = TILE_SIZE * blockIdx.y + threadIdx.y;
for (int i = 0; i< size / TILE_SIZE; i++)
{
shared_a[threadIdx.y][threadIdx.x] = d_a[row* size + (i*TILE_SIZE + threadIdx.x)];
shared_b[threadIdx.y][threadIdx.x] = d_b[(i*TILE_SIZE + threadIdx.y) * size + col];
__syncthreads();
for (int j = 0; j<TILE_SIZE; j++)
d_c[row*size + col] += shared_a[threadIdx.y][j] * shared_b[j][threadIdx.x];
__syncthreads();
}
}
// main routine
int main()
{
const int size = 4;
//Define Host Array
float h_a[size][size], h_b[size][size],h_result[size][size];
//Defining device Array
float *d_a, *d_b, *d_result;
//Initialize host Array
for (int i = 0; i<size; i++)
{
for (int j = 0; j<size; j++)
{
h_a[i][j] = i;
h_b[i][j] = j;
}
}
cudaMalloc((void **)&d_a, size*size*sizeof(int));
cudaMalloc((void **)&d_b, size*size * sizeof(int));
cudaMalloc((void **)&d_result, size*size* sizeof(int));
//copy host array to device array
cudaMemcpy(d_a, h_a, size*size* sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(d_b, h_b, size*size* sizeof(int), cudaMemcpyHostToDevice);
//Define grid and block dimensions
dim3 dimGrid(size / TILE_SIZE, size / TILE_SIZE, 1);
dim3 dimBlock(TILE_SIZE, TILE_SIZE, 1);
//gpu_Matrix_Mul_nonshared << <dimGrid, dimBlock >> > (d_a, d_b, d_result, size);
gpu_Matrix_Mul_shared << <dimGrid, dimBlock >> > (d_a, d_b, d_result, size);
cudaMemcpy(h_result, d_result, size*size * sizeof(int), cudaMemcpyDeviceToHost);
printf("The result of Matrix multiplication is: \n");
for (int i = 0; i< size; i++)
{
for (int j = 0; j < size; j++)
{
printf("%f ", h_result[i][j]);
}
printf("\n");
}
cudaFree(d_a);
cudaFree(d_b);
cudaFree(d_result);
return 0;
}