CUDA C++ 記憶體管理 (CUDA C++ Memory Management)

要在 GPU 上執行 vecAdd 這類 kernel,輸入/輸出陣列(A、B、C)必須位於 GPU 可存取的記憶體中。CUDA 提供多種做法,本筆記聚焦兩種最基本的方式:Unified Memory(由 driver 自動搬移)與 Explicit Memory Management(程式設計師手動配置與複製),並比較兩者在效能上的取捨。

重點總覽

項目 重點
問題本質 kernel 要存取的資料必須在 GPU 可存取的 memory;兩種主要做法:unified vs explicit
Unified Memory cudaMallocManaged(或 __managed__ specifier)配置;單一指標 CPU/GPU 皆可存取,driver 自動搬移
cudaFree unified 與 explicit 的 device buffer 都用同一個 cudaFree 釋放
Explicit Management cudaMalloc 配置 device memory,cudaMemcpy 手動搬移;host pointer 與 device pointer 分離
cudaMemcpy 參數:dest、src、size(bytes)、cudaMemcpyKind_t同步(複製完才返回)
Copy 方向 cudaMemcpyHostToDevice / DeviceToHost / DeviceToDevice / Default(依指標自動判斷)
Page-locked memory cudaMallocHost 配置 page-locked host buffer,提升複製效能、async 傳輸的必要條件
效能取捨 explicit 較冗長但可精確控制搬移時機/位置,並與運算重疊;unified 較簡潔,可用 advise/prefetch 補回部分效能

2.1.3 Memory in GPU Computing(總覽)

要使用前面定義的 vecAdd kernel,陣列 A、B、C 必須放在 GPU 可存取的 memory 中。本章示範兩種做法,其餘(如更進階的 unified memory 機制)留待後續章節。

            「資料要讓 GPU 看得到」的兩條路徑
  ┌─────────────────────────────┬─────────────────────────────┐
  │      Unified Memory         │   Explicit Management        │
  │  cudaMallocManaged          │   cudaMalloc + cudaMemcpy     │
  │  → 單一指標, driver 自動搬移 │   → host/device 指標分離,手動搬 │
  │  簡潔、好寫                  │   冗長,但可精確控制與重疊      │
  └─────────────────────────────┴─────────────────────────────┘
Tip

兩種方式並非互斥;可先用 unified memory 快速寫出正確版本,再針對熱點改用 explicit management(或加上 prefetch/advise)來調效能。

2.1.3.1 Unified Memory

Unified Memory 是 CUDA runtime 的功能,讓 NVIDIA Driver 負責管理 host 與 device(s) 之間的資料搬移。記憶體用 cudaMallocManaged API 配置,或用 __managed__ specifier 宣告變數。Driver 會確保:當 CPU 或 GPU 任一方嘗試存取該記憶體時,記憶體都是可存取的

關鍵事實:

void unifiedMemExample(int vectorLength)
{
    float *A = nullptr, *B = nullptr, *C = nullptr;
    // 用 unified memory 配置:CPU/GPU 皆可存取
    cudaMallocManaged(&A, vectorLength * sizeof(float));
    cudaMallocManaged(&B, vectorLength * sizeof(float));
    cudaMallocManaged(&C, vectorLength * sizeof(float));

    initArray(A, vectorLength);          // host 直接初始化
    initArray(B, vectorLength);

    int threads = 256;
    int blocks  = cuda::ceil_div(vectorLength, threads);
    vecAdd<<<blocks, threads>>>(A, B, C, vectorLength);  // 同一指標傳給 GPU
    cudaDeviceSynchronize();             // 等 kernel 完成

    // CPU 直接讀 C 做比對,無需 cudaMemcpy
    cudaFree(A); cudaFree(B); cudaFree(C);
}

重點:同一個指標 A/B/C 既給 host 初始化也給 kernel 使用,driver 自動保證資料在被存取時就在正確的位置。

Warning

例外:在某些 Linux 系統上(例如具備 address translation servicesheterogeneous memory management 者),所有 system memory 自動就是 unified memory,此時無需使用 cudaMallocManaged__managed__ specifier。

2.1.3.2 Explicit Memory Management

Explicit memory management 由程式設計師明確控制記憶體配置與 host/device 間的資料搬移。優點是可改善效能,缺點是程式碼更冗長。device memory 用 cudaMalloc 配置、用 cudaFree 釋放(與 unified 相同)。

關鍵事實:

// host buffer 用 page-locked memory(複製效能較佳)
cudaMallocHost(&A, vectorLength * sizeof(float));   // ... B, C 同理
initArray(A, vectorLength); initArray(B, vectorLength);

// device buffer
cudaMalloc(&devA, vectorLength * sizeof(float));     // ... devB, devC 同理
// host → device
cudaMemcpy(devA, A, vectorLength * sizeof(float), cudaMemcpyDefault);
cudaMemcpy(devB, B, vectorLength * sizeof(float), cudaMemcpyDefault);
cudaMemset(devC, 0, vectorLength * sizeof(float));

vecAdd<<<blocks, threads>>>(devA, devB, devC, vectorLength);
cudaDeviceSynchronize();

cudaMemcpy(C, devC, vectorLength * sizeof(float), cudaMemcpyDefault);  // device → host
cudaFree(devA); /* ... */ cudaFreeHost(A); /* ... */

重點:相比 unified 版本,多了 cudaMalloc + 兩段 cudaMemcpy(上傳輸入、下載結果),這正是「冗長但可控」的代價。

cudaMemcpy 與複製方向

cudaMemcpy 的參數依序為:destination pointer、source pointer、size in bytes、cudaMemcpyKind_tcudaMemcpyKind_t 常見值:

Kind 方向
cudaMemcpyHostToDevice CPU → GPU
cudaMemcpyDeviceToHost GPU → CPU
cudaMemcpyDeviceToDevice GPU 內部,或 GPU↔GPU
cudaMemcpyDefault 由 source/dest 指標位址自動判斷複製類型
   host buffer (pageable/pinned)            device buffer (global memory)
        A ──cudaMemcpyHostToDevice──▶ devA
        B ──cudaMemcpyHostToDevice──▶ devB
        C ◀──cudaMemcpyDeviceToHost── devC   (kernel 寫完結果後回拷)
Important

cudaMemcpy同步(synchronous) 的:在複製完成前不會返回,會阻塞呼叫它的 host thread。非同步複製(async copy)另見 streams 章節 02-Programming-GPUs/14-Async-Streams-and-Events

Tip

cudaMemcpyDefault 因為靠 unified virtual address space 由指標判斷方向,使用上最省心;但寫明確方向(HostToDevice 等)能讓意圖更清楚、也便於除錯。

Page-locked (pinned) host memory

Warning

例外/陷阱:若過多 host memory 被 page-lock,某些系統上效能會反而下降。最佳做法是對真正用於 GPU 收/送資料的 buffer 做 page-lock,而非全部 host memory。

2.1.3.3 Memory Management and Application Performance

如範例所見,explicit memory management 較冗長,需要程式設計師自行指定 host/device 間的複製。這同時是它的優點與缺點

面向 Explicit Unified
程式碼量 冗長(手寫 malloc/memcpy) 簡潔(單一指標)
搬移時機控制 由程式設計師精確控制 由 driver 自動決定
資料 residency / 配置位置 完全掌握「資料在哪、配置在哪」 driver 管理
與運算重疊 可控制傳輸並與其他運算重疊 預設不易
調效手段 直接控制 transfer Memory Advise / Prefetch 提示 driver
Tip

共通的效能原則:避免不必要的 host↔device 搬移。資料一旦在 GPU 上,盡量留在 GPU 連續做多個 kernel,再一次性拷回;無論 explicit 或 unified(搭配 prefetch)都遵循此原則。

   反例(搬移過多)           正例(資料常駐 GPU)
   H→D → kernel1 → D→H        H→D → kernel1 → kernel2 → kernel3 → D→H
   H→D → kernel2 → D→H              (中間不來回搬)
   H→D → kernel3 → D→H
   每步都來回 PCIe/NVLINK     只進/出一次,傳輸可與運算重疊

考試/測驗重點

情境/關鍵字 答案
配置 CPU/GPU 皆可存取的單一指標 buffer cudaMallocManaged(或 __managed__ specifier)
unified buffer 用什麼釋放? cudaFree(與 device memory 相同)
explicit:配置 device memory / 釋放 cudaMalloc / cudaFree
explicit:配置 page-locked host memory / 釋放 cudaMallocHost / cudaFreeHost
cudaMemcpy 的最後一個參數型別 cudaMemcpyKind_t
cudaMemcpy 參數順序 dest, src, size(bytes), kind
讓 CUDA 依指標自動判斷複製方向 cudaMemcpyDefault
GPU→CPU 的方向常數 cudaMemcpyDeviceToHost
CPU→GPU 的方向常數 cudaMemcpyHostToDevice
GPU 內或 GPU↔GPU cudaMemcpyDeviceToDevice
cudaMemcpy 是同步還是非同步? 同步;複製完才返回(會阻塞 host thread)
非同步傳輸的前提 host buffer 須為 page-locked(cudaMallocHost
page-lock 太多會怎樣? 某些系統效能反而下降;只 page-lock 用於傳輸的 buffer
unified memory 一定要 cudaMallocManaged 嗎? 例外:某些 Linux(ATS/HMM)所有 system memory 自動是 unified,無需呼叫
unified memory 的調效 API Memory Advise / Prefetch(給 driver hints)
explicit vs unified 的根本差別 explicit 手動控制搬移/residency 但冗長;unified 由 driver 自動但較難精細調效
host pointer 可否直接傳給 kernel(explicit)? 否;kernel 收的是 device pointer(devA 等),兩者位址空間分離
共通效能準則 避免不必要的 host↔device 搬移,讓資料常駐 GPU、傳輸與運算重疊