CUDA 프로그래밍(4): 메모리 관리

메모리

기억에 대한 기본 지식

일반적으로 Registers——Caches——Main Memory——Disk Memory, 속도는 점차 감소하고 용량은 점차 증가합니다.
여기에 이미지 설명 삽입
메모리는 프로그램 가능한 메모리와 프로그램 불가능한 메모리로 나눌 수 있습니다. 프로그래밍 가능한 메모리는 사용자가 이 메모리를 읽고 쓸 수 있음을 의미하고, 비프로그래머블 메모리는 사용자에게 개방되지 않은 메모리를 의미하며 프로세스 속도를 높이는 규칙입니다.

CPU와 GPU의 메모리 구조에서 1차 및 2차 캐시(Cache)는 프로그래밍이 불가능한 저장 장치입니다.

GPU 메모리 구조

각 쓰레드는 자신의 sum 을 가지고 있고 , resigters블록 local memory그것을 가지고 있습니다 . 메모리마다 범위, 수명 및 캐시 동작이 다릅니다.shared memoryconstant memorytexture memoryGlobal memoryCache

메모리 위치 캐시 여부 액세스 권한 가변 수명
등록하다 없음 장치 읽기/쓰기 스레드와 동일
로컬 메모리 온보드 없음 장치 읽기/쓰기 스레드와 동일
공유 메모리 없음 장치 읽기/쓰기 블록과 동일
상수 메모리 온보드 가지다 장치 읽기 전용, 호스트 읽기/쓰기 프로그램에서 유지할 수 있습니다.
텍스처 메모리 온보드 가지다 장치 읽기 전용, 호스트 읽기/쓰기 프로그램에서 유지할 수 있습니다.
글로벌 메모리 온보드 없음 장치 읽기/쓰기, 호스트 읽기/쓰기 프로그램에서 유지할 수 있습니다.

등록자 등록

레지스터는 가장 빠른 메모리 공간으로, CPU와 달리 GPU는 더 많은 레지스터를 보유하고 있습니다. 커널 함수에서 변수를 수정하지 않고 선언하면 레지스터에 변수가 저장되고 커널 함수에서 정의한 상수 길이의 배열도 레지스터에서 주소를 할당받는다.

레지스터는 각 스레드에 대해 비공개입니다. 레지스터는 일반적으로 자주 사용되는 비공개 변수를 저장합니다. 레지스터 변수의 수명 주기는 커널 함수와 동일하며 작업 시작부터 작업 종료까지입니다.

레지스터는 SM에서 희소한 리소스입니다. Fermi 아키텍처에는 스레드당 최대 63개의 레지스터가 있고 Kepler 아키텍처에는 최대 255개의 레지스터가 있습니다. 스레드가 더 적은 레지스터를 사용하면 더 많은 상주 스레드 블록이 있을 것입니다 SM에 동시 스레드 블록이 많을수록 효율성이 높아지고 성능 및 활용률이 높아집니다 따라서 프로그래밍할 때 레지스터를 적게 사용하는 것이 좋습니다 .더 적은 레지스터를 사용하십시오.

스레드에 너무 많은 변수가 있어 레지스터가 전혀 충분하지 않고 이때 레지스터가 오버플로되면 로컬 메모리가 추가 변수를 저장하는 데 도움이 되므로 효율성에 매우 부정적인 영향을 미칩니다.

로컬 메모리 로컬 메모리

레지스터에 저장되어 있지만 커널 함수가 할당한 레지스터 공간에 들어갈 수 없는 커널 함수 내의 변수는 로컬 공간에 저장되며, 컴파일러가 로컬 메모리에 저장할 수 있는 변수는 다음과 같다.

  • 알 수 없는 인덱스로 참조되는 로컬 배열입니다.
  • 많은 레지스터 공간을 차지할 수 있는 대형 로컬 배열 또는 구조.
  • 커널 레지스터 자격을 충족하지 않는 모든 변수.

로컬 메모리는 본질적으로 글로벌 메모리와 동일한 저장 영역에 있으며 액세스는 높은 대기 시간과 낮은 대역폭이 특징입니다.

2.0 이상의 장치의 경우 로컬 메모리는 각 SM의 L1 캐시 또는 장치의 L2 캐시에 저장됩니다.

공유 메모리 공유 메모리

커널 함수에서 다음 수정자를 사용하는 메모리를 공유 메모리라고 합니다 __shared__.

각 SM은 스레드 블록에 의해 할당된 일정량의 공유 메모리를 가지고 있습니다.공유 메모리는 온칩 메모리이며, 메인 메모리에 비해 속도가 훨씬 빠릅니다. 즉, 지연이 낮고 대역폭이 높습니다. L1 캐시와 유사하지만 프로그래밍이 가능합니다. 공유 메모리 사용 시 과도한 공유 메모리 사용으로 인해 SM의 활성 워프 수가 줄어들지 않도록 주의하십시오.

공유메모리는 커널함수에서 선언되며 수명주기는 쓰레드 블록과 동일하며, 쓰레드 블록이 실행되기 시작하면 이 블록의 공유 메모리가 할당되고 쓰레드 블록 실행이 끝나면 공유 메모리가 할당된다. 메모리가 해제됩니다.

공유 메모리는 블록 내 쓰레드만 볼 수 있고 다른 쓰레드 블록이 접근할 수 없기 때문에 경쟁 문제가 있고, 공유 메모리를 통해서도 통신이 가능하다. 메모리 경쟁을 피하기 위해 동기화 문을 사용할 수 있습니다 void __syncthreads();.이 문은 스레드 블록이 실행될 때 각 스레드의 장애물 지점에 해당합니다.블록의 모든 스레드가 이 장애물 지점까지 실행되면 다음 계산을 할 수 있습니다. 수행됩니다. 그러나 자주 사용하면 커널 실행 효율성에 영향을 미칩니다.

공유 메모리는 고속 병렬 액세스를 위해 동일한 크기의 메모리 블록으로 나뉩니다.
은행: 분할 방식입니다. CPU에서 메모리 접근은 특정 주소에 접근하여 그 주소에 대한 데이터를 얻는 것이지만 여기서는 여러 뱅크의 주소에 한 번에 접근하여 이 주소에 있는 모든 데이터를 얻어 논리적으로 매핑하는 것이다. 다른 은행에. 메모리 읽기 제어와 유사합니다.
동시 고대역폭 메모리 액세스를 달성하기 위해 공유 메모리는 동시에 액세스할 수 있는 동일한 크기의 메모리 블록(뱅크)으로 나뉩니다. 따라서 메모리에서 n개의 주소를 읽고 쓰는 행위는 독립적인 b개의 뱅크가 동시에 작동하는 방식으로 수행될 수 있으므로 유효 대역폭은 뱅크의 b배로 증가합니다.
여러 스레드에서 요청한 메모리 주소가 동일한 뱅크에 매핑되면 이러한 요청이 직렬화(직렬화)됩니다. 하드웨어는 이러한 요청을 충돌 없이 x개의 요청 시퀀스로 분할하여 대역폭을 x배로 줄입니다. 그러나 워프의 모든 스레드가 동일한 메모리 주소에 액세스하면 브로드캐스트(보드캐스트)가 생성되고 이러한 요청은 한 번에 완료됩니다. Computing Capability 2.0 이상의 장치에는 멀티캐스트 기능도 있으며 동일한 워프에서 동일한 메모리 주소에 액세스하는 일부 스레드의 요청에 동시에 응답할 수 있습니다.

상수 메모리 상수 메모리

상수 메모리는 장치 메모리에 상주하며 각 SM에는 전용 상수 메모리 캐시, 상수 메모리 사용량이 있습니다 __constant__.

상수 메모리는 커널 함수 외부에서 선언되고 전역 범위에서 선언됩니다. 모든 장치에 대해 일정량의 상수 메모리만 선언할 수 있습니다. 상수 메모리는 정적으로 선언되며 동일한 컴파일 단위의 모든 커널 함수에 표시됩니다. 상수 메모리는 호스트에 의해 초기화된 후 커널 기능에 의해 수정될 수 없습니다.

텍스처 메모리 텍스처 메모리

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

글로벌 메모리 글로벌 메모리

글로벌 메모리는 GPU 코어와 독립된 하드웨어 RAM, 즉 우리가 종종 비디오 메모리라고 부르는 것입니다.GPU 메모리 공간의 대부분은 글로벌 메모리입니다. 글로벌 메모리는 GPU에서 가장 큰 메모리 공간이며 대기 시간이 가장 길고 가장 일반적인 메모리를 사용합니다. 글로벌은 범위와 생명주기를 말하며 일반적으로 호스트 측 코드에서 정의되며 장치 측에서도 정의할 수 있습니다.단 수정자가 필요하며 파괴되지 않는 한 동일한 수명에 속합니다. 응용 프로그램으로 순환하십시오.

캐시 캐시

GPU 캐시는 프로그래밍할 수 없는 메모리이며 GPU에는 4가지 유형의 캐시가 있습니다.

  • L1 캐시
  • L2 캐시
  • 읽기 전용 상수 캐시
  • 읽기 전용 텍스처 캐시

각 SM에는 첫 번째 수준 캐시가 있고 모든 SM은 두 번째 수준 캐시를 공유합니다. 첫 번째 및 두 번째 수준 캐시의 기능은 레지스터 오버플로 데이터를 포함하여 로컬 메모리 및 전역 메모리에 데이터를 저장하는 데 사용됩니다. 각 SM에는 읽기 전용 상수 캐시와 읽기 전용 텍스처 캐시가 있으며, 이는 해당 메모리 공간에서 읽기 성능을 향상시키기 위해 장치 메모리에서 사용됩니다.

CPU와 달리 CPU 읽기 및 쓰기 프로세스는 캐시될 수 있지만 GPU 쓰기 프로세스는 캐시되지 않고 읽기 프로세스만 캐시됩니다.

GPU 메모리 할당, 해제 및 전송

CUDA 프로그램은 GPU 메모리와 CPU 메모리를 사용하며, CPU 메모리 할당 및 해제는 new 및 delete(C++), malloc, calloc 및 free(C)를 사용할 수 있습니다. GPU 메모리의 할당 및 해제는 CUDA에서 제공하는 라이브러리 기능을 사용하여 구현됩니다. 동시에 두 메모리는 서로 독립적이기 때문에 전송을 실현하려면 데이터를 다른 메모리에 복사해야 합니다.

메모리 데이터 할당

\\ 分配设备上的内存。
cudaError_t cudaMalloc(void** devPtr, size_t size)

cudaMalloc이 함수는 장치에 메모리를 할당하는 데 사용되며 호스트에서 호출해야 합니다(즉, CPU에서 실행하는 코드에서 호출됨). 반환 값은 cudaError_t가능한 모든 오류 상황을 열거하는 열거형입니다. 그리고 함수 호출이 성공하면 를 반환합니다 cudaSuccess. 첫 번째 매개변수의 유형은 void **할당된 메모리의 첫 번째 주소를 가리키는 입니다. 두 번째 매개변수의 유형은 size_t할당할 메모리의 크기를 바이트 단위로 지정하는 입니다.

메모리 데이터 릴리스

\\ 释放先前在设备上申请的内存空间。
cudaError_t cudaFree(void* devPtr)

cudaFree이 함수는 장치에서 이전에 할당된 메모리 공간을 해제하는 데 사용되지만 malloc을 통해 할당된 메모리는 해제할 수 없습니다. 반환 유형은 여전히 ​​입니다 cudaError_t. 기능 매개변수는 해제해야 하는 장치 메모리의 첫 번째 주소를 가리킵니다.

메모리 데이터 전송

호스트 메모리와 장치 메모리 사이의 데이터 동기 전송을 완료하려면 cudaMemcpy다음 기능을 사용해야 합니다.

\\ 数据同步拷贝。
cudaError_t cudaMemcpy(void* dst, const void* src, size_t count, cudaMemcpyKind kind)

호스트 메모리와 장치 메모리 사이의 비동기 데이터 전송을 완료하려면 cudaMemcpyAsync다음 기능을 사용해야 합니다.

\\ 数据异步拷贝
cudaError_t cudaMemcpyAsync(void* dst, const void* src, size_t count, cudaMemcpyKind kind, cudaStream_t stream = 0)

stream이 0이 아닌 경우 다른 스트림 작업과 겹칠 수 있습니다.

cudaMemcpyKind데이터 전송 방향을 나타내며 다음과 같은 옵션이 있습니다.

  • cudaMemcpyHostToHost
  • cudaMemcpyHostToDevice
  • cudaMemcpyDeviceToHost
  • cudaMemcpyDeviceToDevice

오류 처리

거의 모든 CUDA API 함수는 cudaError_t 유형의 값을 반환하므로 함수 호출의 성공 여부를 나타내는 데 사용됩니다. 반환 값이 cudaSuccess이면 함수 호출이 성공한 것입니다. 실패하면 반환 값은 실패의 특정 코드를 표시하고 프로그래머는 cudaGetErrorString 함수를 통해 특정 오류 메시지를 얻을 수 있습니다. 따라서 코드의 아름다움을 잃지 않고 프로그램의 견고성을 높이고 오류 수정을 용이하게 하기 위해서는 GPUAssert()매크로 기능을 사용하는 것이 좋습니다.
예를 들어:

GPUAssert(cudaMalloc(&dev_a, sizeof(int)));

추천

출처blog.csdn.net/weixin_43603658/article/details/129912525