CUDA 프로그래밍 - 공통 메모리의 분류 및 도입

CUDA 메모리는 물리적으로 두 가지 범주로 나눌 수 있습니다.

  • 온보드 메모리

  • 온칩 메모리

온보드 비디오 메모리는 주로 글로벌 메모리, 로컬 메모리, 상수 메모리 및 텍스처 메모리를 포함하고 온칩 메모리는 주로 레지스터 및 공유 메모리를 포함합니다. 주요 기능은 아래 표에 나열되어 있습니다.

메모리 위치 캐시 여부 액세스 권한 가변 수명
등록하다 없음 장치 읽기/쓰기 스레드와 동일
로컬 메모리 온보드 없음 장치 읽기/쓰기 스레드와 동일
공유 메모리 없음 장치 읽기/쓰기 블록과 동일
상수 메모리 온보드 가지다

장치 읽기 전용

호스트 읽기/쓰기

프로그램에서 유지할 수 있습니다.
텍스처 메모리 온보드 가지다

장치 읽기 전용

호스트 읽기/쓰기

프로그램에서 유지할 수 있습니다.
글로벌 메모리 온보드 없음

장치 읽기/쓰기

호스트 읽기/쓰기

프로그램에서 유지할 수 있습니다.

온칩 메모리의 읽기 및 쓰기 효율성은 일반적으로 온보드 비디오 메모리보다 빠르며 레지스터는 모든 비디오 메모리 유형 중에서 가장 빠른 메모리입니다. 이 기사에서는 이러한 메모리의 응용 시나리오와 사용법을 각각 소개합니다.


01

레지스터 및 로컬 메모리

코드 구현의 관점에서 레지스터 변수와 로컬 메모리 변수는 같은 방식으로 정의되며 모두 C/C++ 함수의 로컬 변수 정의와 마찬가지로 CUDA 커널 함수에서 정의된 변수입니다.예를 들어 다음 커널 함수 코드에서 변수 A, 인덱스 및 tmp는 모두 레지스터 변수 또는 지역 변수에 속합니다.

__global__ void cuda_kernel(float *a, float b, float *c, int row, int col)
{
    int x = threadIdx.x + blockDim.x * blockIdx.x;  //col
    int y = threadIdx.y + blockDim.y * blockIdx.y;  //row
    
    if(x < col && y < row)
    {
        float A[200];
        int index = y*col + x;
        float tmp = a[index]*b[index] + b[index]*b[index];
        c[index] = tmp*tmp;
    }
}

그렇다면 CUDA 커널 함수에 정의된 변수는 언제 레지스터 변수이고 언제 로컬 변수입니까?

일반적으로 다음 세 가지 경우에 정의된 변수는 지역 변수이고 나머지는 레지스터 변수입니다.

  • 컴파일러는 컴파일 단계에서 배열의 값을 결정할 수 없습니다.이 경우 배열은 로컬 변수입니다 . 즉, 데이터 내용은 로컬 메모리에 저장됩니다.

  • 배열이나 구조가 큰 메모리 공간을 차지하는 경우 시스템은 이를 로컬 메모리, 즉 로컬 변수에 할당합니다.

  • 레지스터 공간이 매우 작기 때문에 커널 함수에 많은 변수가 정의되어 있으면 레지스터 공간의 한계를 초과하는 변수는 로컬 메모리, 즉 로컬 변수에 할당됩니다.

위에서 알 수 있듯이 일반적으로 커널 함수에는 너무 많거나 너무 큰 변수가 정의되어 있습니다. 그렇지 않으면 시스템이 레지스터 제한을 초과하는 변수를 자동으로 로컬 메모리에 할당하여 프로그램 작업의 효율성에 영향을 미칩니다.


02

공유 메모리

공유 메모리의 주요 기능은 "공유"입니다. 즉, 동일한 스레드 블록의 모든 스레드가 이 스토리지를 읽고 쓸 수 있으므로 "공유"는 동일한 스레드 블록의 모든 스레드를 나타냅니다. 공유 메모리가 정의되고 크기가 지정되면 시스템은 크기가 8바이트인 unsigned char 공유 메모리를 정의하는 것과 같이 모든 스레드 블록에 동일한 크기의 공유 메모리를 할당한 다음 모든 스레드 블록에 할당됩니다. 8바이트의 부호 없는 문자 유형 공유 메모리.

위에서 공유 메모리의 특성과 응용 프로그램에 대해 자세히 소개했으므로 여기서는 반복하지 않겠습니다.

CUDA 가속 - 공유 메모리 소개 및 응용


03

상수 메모리

상수 메모리는 장치 측(GPU 측)에서는 읽기 전용이고 호스트 측(CPU 측)에서는 읽고 쓸 수 있습니다.일반적으로 __constant__로 데코레이트된 변수는 데이터를 상수 메모리에 저장하는 데 사용되며 변수는 전역 변수 동일한 .cu 파일에서 그 뒤에 정의된 모든 커널 함수를 볼 수 있습니다. 예를 들어, 다음 코드에서 변수 A의 값은 상수 메모리에 저장되고 kernel1과 kernel2 모두 A를 사용할 수 있지만 kernel0은 A보다 먼저 정의되었기 때문에 A를 사용할 수 없습니다.

__global__ void kernel0()
{


}


__constant__ float A[128];


__global__ void kernel1()
{


}


__global__ void kernel2()
{


}

상수 메모리는 장치 측에서 읽기 전용이므로 다음 코드와 같이 cudaMemcpyToSymbol 함수를 호출하여 호스트 측에서만 초기화 및 수정할 수 있습니다.

__constant__ float A[10];


void init_constant(void)
{
     float B[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
     //数组A定义在device端的常量内存,数组B定义在host端,使用数组B的内容初始化数组A
     cudaMemcpyToSymbol(A, B, 10*sizeof(float));  
}

언제 상수 메모리를 사용합니까?

먼저 워프와 하프워프의 개념에 대해 이야기해 봅시다.

소프트웨어 관점에서 한 블록에 포함된 최대 스레드 수는 일반적으로 512 또는 1024이며 여러 블록에 포함된 모든 스레드는 병렬로 실행됩니다. 그러나 하드웨어적으로는 그렇지 않고 하드웨어적으로는 각 블록의 모든 쓰레드를 32개의 쓰레드 워프 그룹으로 나누고, 쓰레드 워프를 워프라고 부른다. 동일한 워프의 스레드는 실제로 병렬이며 자체 데이터를 사용하여 동일한 처리 명령을 실행합니다. 서로 다른 워프의 실행은 시스템에 의해 예약되므로 서로 다른 워프가 반드시 병렬로 실행되는 것은 아닙니다.

이름에서 알 수 있듯이 하프 워프는 하프 워프, 즉 동일한 워프에 16개의 스레드가 있습니다.

409092c932d5b6d7f59666ada42c7603.png

다음으로 상수 메모리 사용의 이점을 소개합니다. 두 가지 주요 이점이 있습니다.

  • Half-warp에 포함된 16개의 스레드의 경우 GPU는 상수 메모리에서 한 번의 읽기 작업만 수행하면 되며 이 16개의 스레드는 모두 16번의 읽기 작업을 수행하지 않고도 데이터를 얻을 수 있습니다.

  • 상수 메모리에는 캐시 메커니즘이 있습니다.GPU가 상수 메모리의 주소에 액세스하면 해당 주소의 값이 캐시됩니다.주소에 다시 액세스하면 캐시에서 직접 값을 얻을 필요가 없습니다. 다른 읽기 작업을 수행하므로 속도가 훨씬 빨라집니다.

각 하프 워프에서 16개의 스레드가 동일한 메모리 주소에 액세스해야 할 때 상수 메모리를 사용하면 효율성이 크게 향상될 수 있지만 각 하프 워프에서 16개의 스레드가 서로 다른 메모리 주소에 액세스해야 하는 경우 이 In 이 경우 상수 메모리를 사용하는 것은 적합하지 않습니다.


04

글로벌 메모리

글로벌 메모리는 GPU에서 가장 큰 용량을 가진 메모리로 10GB에 이를 수 있어 CUDA 프로그래밍은 일반적으로 더 큰 데이터를 글로벌 메모리에 저장하므로 글로벌 메모리는 모든 GPU 메모리 중에서 가장 일반적으로 사용되는 메모리이기도 합니다. 아래에서는 글로벌 메모리의 적용, 복사, 사용 및 해제를 각각 소개합니다.

  • 글로벌 메모리 적용

cudaMalloc 함수를 호출하여 쉽게 전역 메모리를 신청할 수 있지만, 이 함수의 세 번째 매개변수의 단위가 바이트라는 점에 주의해야 하므로 데이터 유형이 char/unsigned char 유형이 아닌 경우 세 번째 매개변수는 데이터 길이에 데이터 유형 바이트 수(sizeof)를 곱하려면:

//定义指针
unsigned char *A;
int *B;
float *C;
//定义数据长度
const int data_len = 128;
//申请全局内存
cudaMalloc((void**)&A, data_len * sizeof(unsigned char));
cudaMalloc((void**)&B, data_len * sizeof(int));
cudaMalloc((void**)&C, data_len * sizeof(float));
  • 글로벌 메모리 사본

cudaMemcpy 함수는 일반적으로 호스트 측 메모리에서 장치 측 전역 메모리로 또는 장치 측 전역 메모리에서 호스트 측 메모리로 데이터를 복사하기 위해 호출됩니다. 마찬가지로 이 함수의 세 번째 매개변수의 단위도 바이트이므로 세 번째 매개변수는 데이터 길이에 데이터 유형이 차지하는 바이트 수를 곱해야 합니다.

float A[8] = {0, 1, 2, 3, 4, 5, 6, 7};
float *C;


cudaMalloc((void**)&C, 8 * sizeof(float));
//将数据从host端内存拷贝到device端全局内存
cudaMemcpy(C, A, 8 * sizeof(float), cudaMemcpyHostToDevice);
//将数据从device端全局内存拷贝到host端内存
cudaMemcpy(A, C, 8 * sizeof(float), cudaMemcpyDeviceToHost);

위 코드에서 4번째 파라미터는 복사 방향을 결정하는데 호스트에서 디바이스로 복사하는 경우 매개변수는 cudaMemcpyHostToDevice 이고 디바이스에서 호스트로 복사하는 경우 매개변수는 cudaMemcpyDeviceToHost 입니다 .

  • 글로벌 메모리의 사용 및 해제

일반적인 CUDA 병렬 작업 프로세스는 다음과 같습니다.

(1) 호스트 측에서 장치 측으로 데이터를 복사합니다(일반적으로 장치 측의 글로벌 메모리로).

(2) 장치 측에서 데이터의 다중 스레드 병렬 처리를 활성화합니다.

(3) 기동할 모든 쓰레드가 데이터를 처리한 후 디바이스 측에서 최종 처리 결과를 다시 호스트 측으로 복사합니다.

d948d783408633114bbb89357fe9d537.png

따라서 글로벌 메모리는 일반적으로 CUDA 커널 함수에서 사용되며 스레드 ID 번호는 일반적으로 글로벌 메모리의 데이터를 인덱싱하는 데 사용되므로 각 스레드는 데이터가 있는 글로벌 메모리 주소와 일대일 대응을 갖습니다. 저장됩니다. 아래에서는 전역 메모리 사용을 설명하기 위해 간단한 예를 제공합니다.

크기가 같은 두 개의 행렬 A와 B가 있고 A와 B가 모두 부동 소수점 데이터 행렬인 경우 이제 CUDA를 사용하여 A와 B의 동일한 좌표점 데이터의 제곱차를 병렬로 계산합니다.

우선, CPU 구현 코드입니다. 모든 점을 반복하여 차이 제곱을 계산합니다.

void CPU_cal(Mat A, Mat B)
{
  Mat C(A.size(), CV_32FC1);
  
  for(int i = 0; i < A.rows; i++)  //行遍历
  {
    float *pA = A.ptr<float>(i);
    float *pB = B.ptr<float>(i);
    float *pC = C.ptr<float>(i);
    
    for(int j = 0; j < A.cols; j++)  //列遍历
    {
      pC[j] = (pA[j] - pB[j])*(pA[j] - pB[j]);
    }
  }


}

그런 다음 멀티스레딩을 시작하고 병렬로 각 점의 제곱 차이를 계산하는 GPU 구현 코드가 있습니다.

/*
CUDA核函数
*/
__global__ void GPU_cal_kernel(float *A_cuda, float *B_cuda, float *C_cuda, int row, int col)
{
  //线程的x方向id
  int x = threadIdx.x + blockDim.x * blockIdx.x;  //col
  //线程的y方向id
  int y = threadIdx.y + blockDim.y * blockIdx.y;  //row
  
  if(x < col && y < row)
  {
    //将线程的二维id转换为全局内存的一维地址索引,并保存到寄存器变量index
    int index = y * col + x;
    
    //根据一一对应关系,线程(x, y)对应全局内存地址index = y * col + x
    //也即线程(x, y)负责处理全局内存地址index保存的数据
    //使用index来索引A_cuda、B_cuda,就相当于从全局内存的index地址读取数据
    //将从A_cuda、B_cuda读取的index地址数据相减,并把差值保存到寄存器变量diff
    float diff = A_cuda[index] - B_cuda[index];
    
    //寄存器变量diff保存了差值,因此计算平方的时候可直接使用diff中保存的值
    //得到平方值之后,再将结果保存到全局内存C_cuda的index位置
    //使用index索引C_cuda,并对其赋值,相当于对全局内存C_cuda的index地址进行写操作
    C_cuda[index] = diff * diff;
  }
}


/*
调用以上核函数
*/
void GPU_cal(Mat A, Mat B)
{
  float *A_cuda, *B_cuda, *C_cuda;
  //计算数据长度,注意如果是byte长度还需再乘以sizeof(float)
  const int data_len = A.rows * A.cols;
  //申请全局内存
  cudaMalloc((void**)&A_cuda, data_len * sizeof(float));
  cudaMalloc((void**)&B_cuda, data_len * sizeof(float));
  cudaMalloc((void**)&C_cuda, data_len * sizeof(float));
  //将数据从host内存拷贝到device全局内存
  cudaMemcpy(A_cuda, (float *)A.data, data_len * sizeof(float), cudaMemcpyHostToDevice);
  cudaMemcpy(B_cuda, (float *)B.data, data_len * sizeof(float), cudaMemcpyHostToDevice);
  //定义线程块、线程块中的线程都为二维索引
  dim3 cuda_Block(16, 16);   //每个线程块有16*16个线程
  int M = (A.cols + cuda_Block.x - 1) / cuda_Block.x;
  int N = (A.rows + cuda_Block.y - 1) / cuda_Block.y;
  dim3 cuda_Grid(M, N);  //线程网格总共有M*N个线程块
  //调用核函数并行处理
  GPU_cal_kernel<<<cuda_Grid, cuda_Block>>>(A_cuda, B_cuda, C_cuda, A.rows, A.cols);
  
  Mat C(A.size(), CV_32FC1);
  //将并行计算结果从device全局内存拷贝到host内存
  cudaMemcpy((float *)C.data, C_cuda, data_len * sizeof(float), cudaMemcpyDeviceToHost);
  //释放申请的全局内存
  cudaFree(A_cuda);
  cudaFree(B_cuda);
  cudaFree(C_cuda);


}

할당된 전역 메모리가 더 이상 사용되지 않으면 cudaFree 함수를 호출하여 해제해야 합니다. 더군다나 위의 코드에서 쓰레드 블록과 쓰레드의 인덱스는 2차원적이며 실제로 이들의 인덱스도 1차원적일 수도 있고 3차원적일 수도 있다. 스레드 블록 및 스레드의 2차원 또는 3차원 인덱스 해당 전역 메모리 주소.

또한 전역 메모리 접근을 위한 병합과 정렬이 있는데 병합과 정렬을 통해서만 전역 메모리에 효율적으로 접근할 수 있다.


05

텍스처 메모리

텍스처 메모리는 GPU의 일종의 읽기 전용 메모리로 글로벌 메모리의 특정 세그먼트를 텍스처 메모리에 바인딩하는 데 사용됩니다.이 글로벌 메모리 세그먼트는 일반적으로 1차원 CUDA 배열/글로벌 메모리, 2차원 또는 3차원 CUDA 배열을 사용한 다음 텍스처 메모리를 읽어 전역 메모리에서 데이터를 가져옵니다(텍스처 가져오기라고도 함). 정렬 및 병합이 필요한 전역 메모리 액세스와 비교할 때 텍스처 메모리는 정렬되지 않은 액세스 및 임의 액세스에 대한 가속 효과가 좋습니다 .

36361a4b1d23f3004ee99a29934f4b1e.png

  • 1D 텍스처

1차원 텍스처는 CUDA 배열에 바인딩하거나 전역 메모리에 직접 바인딩할 수 있습니다. 다음은 1차원 텍스처 메모리의 사용을 소개하는 간단한 예입니다.

첫 번째는 1차원 텍스처의 정의이며 텍스처 메모리는 일반적으로 전역 변수로 정의됩니다.

//float表示数据类型
//cudaTextureType1D、cudaTextureType2D、cudaTextureType3D分别表示一维、二维、三维
//cudaReadModeElementType表示只读模式
//tex_1D为定义的纹理内存变量
texture<float, cudaTextureType1D, cudaReadModeElementType> tex_1D;

둘째, 텍스처 메모리 바인딩입니다. 여기서는 전역 메모리를 텍스처에 직접 바인딩합니다.

const int data_len = 128;
const int data_size = data_len * sizeof(float);
//初始化host端数组
float *data_host = (float *)malloc(data_size);
for(int i = 0; i < data_len; i++)
{
  data_host[i] = i; 
}
//申请device端全局内存
float *data_device;
cudaMalloc((void**)&data_device, data_size);
//将数据从host端拷贝到device端全局内存
cudaMemcpy(data_device, data_host, data_size, cudaMemcpyHostToDevice);
//参数一表示以bytes为单位的偏移量,也即绑定到纹理的全局内存的起始偏移地址
//参数二为纹理内存变量
//参数三为全局内存地址变量
cudaBindTexture(0, tex_1D, data_device);

다음은 tex1Dfetch 함수를 호출하여 실현되는 커널 함수의 텍스처 선택입니다.

//功能:将纹理内存的数据拷贝到全局内存A_cuda
__global__ void cuda_kernel(float *A_cuda, int data_len)
{
  //线程id
  int x = threadIdx.x + blockDim.x * blockIdx.x;
  
  if(x < data_len)
  {
    //使用线程id来索引全局内存A_cuda和纹理内存tex_1D
    A_cuda[x] = tex1Dfetch(tex_1D, x);
  }
}

마지막으로 텍스처 메모리 바인딩이 해제됩니다. 함수가 실행된 후 텍스처 메모리 바인딩을 해제해야 합니다.

cudaUnbindTexture(tex_1D);

전체 코드:

texture<float, cudaTextureType1D, cudaReadModeElementType> tex_1D;


//功能:将纹理内存的数据拷贝到全局内存A_cuda
__global__ void cuda_kernel(float *A_cuda, int data_len)
{
  //线程id
  int x = threadIdx.x + blockDim.x * blockIdx.x;
  
  if(x < data_len)
  {
    //使用线程id来索引全局内存A_cuda和纹理内存tex_1D
    A_cuda[x] = tex1Dfetch(tex_1D, x);
  }
}


void cuda_copy_data(float *data_host_dst)
{
  const int data_len = 128;
  const int data_size = data_len * sizeof(float);
  
  //初始化host端数组
  float *data_host = (float *)malloc(data_size);
  for(int i = 0; i < data_len; i++)
  {
    data_host[i] = i; 
  }
  
  //申请device端全局内存
  float *data_device, *data_dst;
  cudaMalloc((void**)&data_device, data_size);
  cudaMalloc((void**)&data_dst, data_size);
  //将数据从host端拷贝到device端全局内存
  cudaMemcpy(data_device, data_host, data_size, cudaMemcpyHostToDevice);
  
  //参数一表示以bytes为单位的偏移量,也即绑定到纹理的全局内存的起始偏移地址
  //参数二为纹理内存变量
  //参数三为全局内存地址变量
  cudaBindTexture(0, tex_1D, data_device);  
  
  dim3 tex_Block(16);  //每个block有16个线程
  //总共有((data_len + 15) / 16)个block
  dim3 tex_Grid((data_len + tex_Block.x - 1) / tex_Block.x);
  
  //调用核函数
  cuda_kernel<<<tex_Grid, tex_Block>>>(data_dst, data_len);
  
  //将数据从device端拷贝到host端
  cudaMemcpy(data_host_dst, data_dst, data_size, cudaMemcpyDeviceToHost);
  
  //纹理解绑
  cudaUnbindTexture(tex_1D);
  //释放全局内存
  cudaFree(data_device);
  cudaFree(data_dst);
  //释放host内存
  free(data_host);


}
  • 2D 텍스처

일반적으로 2차원 이미지를 저장하는 전역 메모리는 2차원 텍스처에 바인딩되며 2차원 텍스처 데이터는 커널 함수에서 효율적으로 무작위로 액세스할 수 있습니다.

2D 텍스처를 전역 변수로 정의합니다.

texture<float, cudaTextureType2D, cudaReadModeElementType>  tex_src;

CUDA 배열을 정의하고 CUDA 배열 메모리에 적용:

//声明数据类型为float
cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc<float>();
//定义CUDA数组
cudaArray *cuArray_src;
//分配大小为col_c*row_c的CUDA数组
int col_c = 512;
int row_c = 512;
cudaMallocArray(&cuArray_src, &channelDesc, col_c, row_c);

텍스처 메모리 매개변수를 설정하고 CUDA 배열을 텍스처 메모리에 바인딩합니다.

//寻址方式
//cudaAddressModeWrap--循环寻址,如果超出最大地址则转成从最小地址开始
//cudaAddressModeClamp--钳位寻址,如果超出最大地址则访问最大地址
tex_src1.addressMode[0] = cudaAddressModeWrap;
tex_src1.addressMode[1] = cudaAddressModeWrap;
//是否对纹理坐标归一化
tex_src1.normalized = false;
//纹理的滤波模式:
//cudaFilterModePoint--最邻近插值
//cudaFilterModeLinear--双线性插值
tex_src1.filterMode = cudaFilterModePoint;
//纹理绑定,将CUDA数组绑定到纹理tex_src
cudaBindTextureToArray(&tex_src, cuArray_src, &channelDesc);

호스트 메모리에서 CUDA 어레이로 데이터 복사:

Mat M_float = Mat::zeros(row_c, col_c, CV_32FC1);
cudaMemcpyToArray(cuArray_src, 0, 0, (float *)M_float.data, row_c*col_c*sizeof(float), cudaMemcpyHostToDevice);

텍스처 선택을 위해 CUDA 커널 함수에서 tex2D 함수를 호출합니다.

//tex_src--要拾取的纹理内存
//x--纹理内存的x坐标
//y--纹理内存的y坐标
//功能:将纹理内存中(x,y)坐标处的数据加载到寄存器变量d
float d = tex2D(tex_src, x, y);

텍스처 바인딩을 해제하고 CUDA 배열을 해제합니다.

cudaUnbindTexture(tex_src);
cudaFreeArray(cuArray_src);
  • 3D 텍스처

CUDA를 이용하여 멀티 프레임 이미지를 처리하고, 멀티 프레임 시계열 이미지를 GPU로 전송해야 하는 경우가 있는데, 이때 이미지 저장을 위한 글로벌 메모리는 2차원 텍스처 메모리(확장 및 이미지의 각 프레임을 행으로 접합) 커널 함수는 텍스처 가져오기를 통해 입력 이미지 데이터에 액세스합니다. 그러나 2차원 텍스처 메모리의 너비는 다음과 같이 제한됩니다.

cudaMallocArray 함수의 세 번째 매개변수 img_size는 너비, 즉 각 프레임 이미지의 총 데이터 개수입니다. 단, 2차원 텍스처의 너비는 제한되어 있으며 너비가 64K를 초과하면 오류가 발생합니다 . 따라서 각 프레임 이미지의 크기가 64K를 초과하면 2차원 텍스처 메모리를 사용할 수 없으며 이때 3차원 텍스처 메모리를 사용할 수 있습니다.

cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc<float>();
  cudaMallocArray((cudaArray**)&arr_mat_x, &channelDesc, img_size, IIR_N_X);

3D 텍스처의 사용은 1D 및 2D 텍스처의 사용과 상당히 다르며, 3D 텍스처 메모리의 사용에 대해서는 아래에서 자세히 설명합니다.

3D 텍스처를 전역 변수로 정의합니다.

texture<float, cudaTextureType3D, cudaReadModeElementType>  tex_mat;    //定义为3D类型的纹理内存

3차원 CUDA 배열을 정의합니다.

cudaArray *arr_mat;   //定义CUDA数组
cudaExtent extent;   //定义图像的尺寸和帧数结构体
cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc<float>();  //定义数据类型为float
//创建extent时,在旧版本中宽度w以字节为单位,即必须乘上sizeof(DTYPE),新版本已经不用乘以sizeof(DTYPE),否则反而会出错!文档和函数说明都没有改过来,这里是坑!
extent.width = col;     //每帧图像的列数,这里不需要再乘以sizeof(float)
extent.height = row;   //每帧图像的行数
extent.depth = picnum;   //图像的总帧数
//创建picnum帧row*col的存储空间
cudaMalloc3DArray((cudaArray**)&arr_mat, &channelDesc, extent);

호스트 메모리에서 CUDA 배열로 데이터를 복사합니다. 여기서 pic은 picnum 프레임 이미지를 포함하는 Mat 유형의 벡터 배열입니다.

vector<Mat> pic;
cudaMemcpy3DParms HostToDev = {0};   //定义数据传输的结构体
HostToDev.dstArray = arr_mat;    //指定数据传输的目标地址为cuda数组
HostToDev.extent = make_cudaExtent(col, row, 1);      //创建extent时,在旧版本中宽度w以字节为单位,即必须乘上sizeof(DTYPE),新版本已经不用乘以sizeof(DTYPE),否则反而会出错!文档和函数说明都没有改过来,这里是坑!  
HostToDev.kind = cudaMemcpyHostToDevice;  //定义传输方向为CPU到GPU显存
HostToDev.srcPos = make_cudaPos(0, 0, 0);   //定义数据传输的源地址的偏移量(w, h, img_index)
for(int i = 0; i < picnum; i++)   //拷贝多帧图像到cuda数组
{
  //指定数据传输的源地址,注意这里的第二个参数需要乘以数据类型所占的字节数
  HostToDev.srcPtr = make_cudaPitchedPtr((void *)pic[i].data, col*sizeof(float), col, row);        
  HostToDev.dstPos = make_cudaPos(0, 0, i);   //指定目标地址的偏移量,分别为x,y,z地址
  cudaMemcpy3D(&HostToDev);     //根据以上设置的参数实行拷贝
}

3D 텍스처 매개변수를 설정하고 CUDA 배열을 3D 텍스처에 바인딩합니다.

tex_mat.normalized = 0;   //索引地址不归一化
//filterMode:滤波模式。仅对绑定 CUDA 数组的纹理有效。当使用浮点型的坐标寻址纹理时,将根据设定返回不同类型的值。设定可以有:cudaFilterModePoint 和 cudaFilterModeLinear。分别表示最近邻插值和线性插值
tex_mat.filterMode = cudaFilterModePoint;    
tex_mat.addressMode[0] = cudaAddressModeClamp;  //寻址模式,即如何处理越界的纹理坐标。可设置:cudaAddressModeClamp 和 cudaAddressModeWrap。Clamp 即钳位模式,Wrap 为循环模式。循环模式只支持归一化的纹理坐标
tex_mat.addressMode[1] = cudaAddressModeClamp;
tex_mat.addressMode[2] = cudaAddressModeClamp;
tex_mat.channelDesc = channelDesc;   //描述纹理返回值类型,同cuda数组部分的内容
cudaBindTextureToArray(tex_mat, (cudaArray *)arr_mat, channelDesc);   //绑定纹理内存

커널 함수에서 tex3D 함수를 호출하여 텍스처 선택을 수행합니다.

//后面三个参数分别是x,y,z坐标
tex3D(tex_mat, x, y, z);

마지막으로 텍스처는 CUDA 배열을 바인딩 해제하고 해제합니다(2D 텍스처와 동일).

cudaUnbindTexture(tex_mat);
cudaFreeArray(arr_mat);
  • 텍스처 메모리를 위한 하드웨어 보간 기능

텍스처 메모리에는 최근접 이웃 보간 및 쌍선형 보간을 포함한 하드웨어 보간 기능이 있습니다. 텍스처 피킹 중에 입력된 액세스 좌표 주소가 부동 소수점 숫자인 경우 텍스처 메모리는 설정된 보간 방법에 따라 부동 소수점 좌표를 자동으로 보간한 다음 보간 결과를 반환합니다. 이 보간 과정은 개발자가 구현할 필요가 없으며 하드웨어에 의해 자동으로 완료되며, 개발자는 보간 방법을 최근접 이웃 보간 또는 쌍선형 보간으로 설정하기만 하면 되므로 많은 컴퓨팅 시간을 절약할 수 있습니다.

이전에 소개한 바 있습니다. 자세한 내용은 다음을 참조하세요.

CUDA 텍스처 메모리--하드웨어 보간 기능 적용


자, 이 글은 여기까지 하고 다음 글에서는 글로벌 메모리의 얼라인먼트와 병합에 대해 자세히 다루도록 하겠으니 많은 관심 부탁드립니다~

내 WeChat 공개 계정은 다음과 같습니다. 코드를 스캔하여 주의를 기울이고 개인 메시지 기술 교환에 오신 것을 환영합니다.

0d236073fa978d7a40a8ae5519c483e0.png

Guess you like

Origin blog.csdn.net/shandianfengfan/article/details/121240395