C 標準函式庫、Lambda 與多型包裝器 (C Standard Library, Lambdas and Polymorphic Wrappers)

重點總覽

device code 可用一小組 C standard library 函式(clock()/printf()/memcpy()/malloc()/alloca() 等),各有 CUDA 專屬語意與限制。Lambda 的執行空間由最內層 enclosing function 的執行空間決定;要把 lambda 傳進 kernel 必須是 __device____host__ __device__,且常需 extended lambda--extended-lambda 旗標)。Extended lambda 有大量限制(this 捕捉、__CUDA_ARCH__、捕捉方式、return type 內省等)。nvstd::function 提供 host/device 皆可用的多型函式包裝器,但不可跨 host/device 邊界傳遞。

項目 重點
clock() / clock64() 回傳 per-multiprocessor 計數器;因 thread 被 time-sliced,量到的週期數大於實際執行週期
device printf() 每個 thread 各自執行;回傳解析的引數數(非字元數),上限 32 引數;輸出在 host 端格式化
printf buffer 預設 1 MB 環狀緩衝,僅在特定同步/啟動事件時 flush;程式結束不會自動 flush
device malloc()/free() 從 device heap 配置,回傳 16-byte 對齊;預設 heap 8 MB,須在任何配置前用 cudaLimitMallocHeapSize 設定
heap 互通性 device-side malloc/new 配置的記憶體不可cudaFree/cudaMemcpy 操作,反之亦然
alloca() 在 caller stack frame 配置,回傳時自動釋放;device 端 16-byte 對齊
Lambda 執行空間 由最內層 enclosing function 決定;無 enclosing scope 則為 __host__
傳給 __global__ lambda 須為 __device____host__ __device__;global/namespace scope lambda 不可當引數
Extended lambda 定義在 __host__/__host__ __device__ 函式內、帶執行空間標註的 lambda;需 --extended-lambda
捕捉限制 extended lambda 只能 by value 捕捉;init-capture 僅 device-only 支援;陣列維度 ≤ 7
*this 捕捉 C++17 *this by-value 複製物件本身,解決 device 上存取 host this->member 的 runtime error
__CUDA_ARCH__ extended lambda 的存在/數量/捕捉,不可依賴 __CUDA_ARCH__ 是否定義或其值
nvstd::function <nvfunctional> 提供,host/device 皆可用;不可跨 host/device 邊界傳遞或初始化

device 可用的 C standard library functions

device code 只能用一小組 C 標準函式,且多數有 CUDA 專屬語意。建議優先用 <cuda/std/...> 標頭內的 cuda::std:: 對應版本(較安全、可攜)。

函式 執行空間 語意 / 限制
clock() / clock64() clock()__host__ __device__clock64()__device__ 回傳 per-multiprocessor、每週期遞增的計數器;對應 cuda::std::clock()<cuda/std/ctime>
printf() __host__ __device__ __tile__ host 端格式化輸出;見下節
memcpy() / memset() __host__ __device__ __tile__ 複製/填值 size bytes;memset 的 value 視為 unsigned char;建議改用 <cuda/std/cstring>cuda::std::
malloc() / free() __host__ __device__ 從 device heap 配置;見下節
__nv_aligned_device_malloc() __device__ 對齊配置,align 須為非零 2 的次方
alloca() __host__ __device__ 在 caller stack frame 配置,回傳時自動釋放
Tip

計時除了 clock() 外,亦提供可攜的 <cuda/std/chrono> 實作。

device printf()

__host__ __device__ __tile__ int printf(const char* format[, arg, ...]);

Format specifiers%[flags][width][.precision][size]type

欄位 支援
Flags #、空白、0+-
Width *0-9
Precision 0-9
Size hlll
Type %cdiouxXpeEfgGaAs
Warning

  • 最終格式化在 host 端進行,故 format string 必須被 host 編譯器與 C library 理解;無效 flag/type 組合的輸出為 undefined。
  • 上限 32 個引數(不含 format string);多出的引數被忽略,format specifier 原樣輸出。
  • Windows 的 long 是 32-bit、Linux 是 64-bit:在 Linux 編譯、Windows 執行時含 %ld 的輸出會損毀,建議編譯與執行平台一致。

tile code 中的差異:引數可為 tile(每元素依對應 specifier 印出);回傳值永遠是提供的引數數(即使 NULL 或內部錯誤);format string 必須是 literal;引數數與 specifier 數不符會報錯(SIMT device code 中只是 warning)。

Host-side buffer:固定大小、環狀緩衝,預設 1 MBcudaLimitPrintfFifoSize 可調)。滿了會覆蓋舊輸出。只在下列事件 flush:

flush 時機:
  ├─ kernel launch(<<<>>> 或 cuLaunchKernel)啟動時
  │   └─ 若 CUDA_LAUNCH_BLOCKING=1,啟動結束時也 flush
  ├─ 同步:cudaDeviceSynchronize / cuCtxSynchronize / cudaStreamSynchronize
  │        / cuStreamSynchronize / cudaEventSynchronize / cuEventSynchronize
  ├─ blocking memcpy:cudaMemcpy* / cuMemcpy*
  ├─ module load/unload:cuModuleLoad / cuModuleUnload
  ├─ context 銷毀:cudaDeviceReset / cuCtxDestroy
  └─ 執行 cudaLaunchHostFunc / cuLaunchHostFunc 加入的 stream callback 之前
程式結束時 ⇒ 不會自動 flush

device malloc() / free() 與 heap

Important

device heap 大小必須在任何用到 device heap 的程式(含 new/delete)之前指定,否則預設配置 8 MB

  • cudaDeviceGetLimit / cudaDeviceSetLimit 搭配 cudaLimitMallocHeapSize 取得/設定。
  • heap 實際配置發生在 module 載入 context 時;失敗則 module load 回 CUDA_ERROR_SHARED_OBJECT_INIT_FAILED
  • module 載入後 heap 大小不可變、也不會依需求動態調整。
Warning

互通性:device-side malloc/calloc/aligned_alloc/new 配置的記憶體不可用 runtime/driver API(cudaMalloc/cudaMemcpy/cudaMemset/cudaFree)操作或釋放;反之,host runtime API 配置的記憶體也不可用 device-side free()/delete 釋放。device heap 的記憶體獨立於 host 端 cudaMalloc 之外。

常見配置模式:per-thread(每 thread 各自 malloc/free)、per-block(thread 0 配置、經 shared memory 分享指標、僅一 thread free)、跨 kernel 持續(__device__ 全域指標陣列保存配置,多次 kernel 重用後再 free)。

alloca()

__host__ __device__ void* alloca(size_t size);
Note

Windows 上使用前須先 #include <malloc.h>alloca() 可能造成 stack overflow,須自行調整 stack 大小。

Lambda 的執行空間與傳給 kernel

compiler 依 lambda 所在最內層 enclosing function scope 的執行空間決定 lambda(closure type, C++11)的執行空間;若無 enclosing function scope,則為 __host__。也可用 extended lambda 語法明確標註。

lambda 所在處 推導執行空間
global / namespace scope __host__
host_function() __host__
__device__ 函式內 __device__
__global__ 函式內 __device__
__host__ __device__ 函式內 __host__ __device__
__tile__ 函式內 __tile__

傳給 __global__ 函式參數

template <typename T> __global__ void kernel(T input) {}
void host_function() {
  kernel<<<1, 1>>>([] __device__() {});            // 正確,extended lambda
  kernel<<<1, 1>>>([] __host__ __device__() {});   // 正確,extended lambda
  // kernel<<<1, 1>>>([](){});                     // 錯誤,host 執行空間的 closure
  // kernel<<<1, 1>>>(global_lambda);              // 錯誤,extended lambda 但在 global scope
}
Note

__device__ 函式內啟動 device kernel(device-side launch)需要 separate compilation(-rdc=true)。

Extended Lambdas 與其限制

--extended-lambda 旗標允許在 lambda 明確標註執行空間(標註置於 lambda introducer 之後、optional declarator 之前)。指定後 nvcc 定義巨集 __CUDACC_EXTENDED_LAMBDA__

Warning

__device__ 函式內的 lambda(即使標 __device__/__host__ __device__都不是 extended lambda,因為 enclosing function 不是 __host__/__host__ __device__。同理,global scope 的 lambda 也不是 extended lambda。

型別 trait

Trait 回傳 true 的條件
__nv_is_extended_device_lambda_closure_type(T) T 是 extended __device__ lambda 的 closure type
__nv_is_extended_device_lambda_with_preserved_return_type(T) 上者且定義有 trailing return type(且 return type 未引用任何 lambda 參數名)
__nv_is_extended_host_device_lambda_closure_type(T) T 是 extended __host__ __device__ lambda 的 closure type
Note

這些 trait 在任何編譯模式皆可用;extended lambda 模式未啟用時一律回傳 false。

主要限制(精選)

CUDA compiler 在送給 host 編譯器前,會把 extended lambda 換成 namespace scope 的placeholder type,其 template 引數需取得 enclosing function 的位址。由此衍生大量限制:

  1. extended lambda 不可定義在另一個 extended lambda 內。
  2. 不可定義在 generic lambda(帶 auto 參數)內。
  3. 若巢狀在多層 lambda 內,最外層 lambda 必須定義在某(非 lambda)函式的 block scope 內。
  4. enclosing function 必須有名稱且位址可取得;若為 class member:所有 enclosing class 須有名稱,且 member function 與 enclosing class 不可為 private/protected。
  5. 取 enclosing routine 位址須能無歧義(例如 alias 宣告遮蔽同名 template 型別引數時會出錯)。
  6. 不可定義在函式內 local 的 class 中。
  7. enclosing function 不可有 deduced return type(auto 回傳)。
  8. host-device extended lambda 不可是 generic lambda(不可有 auto/auto... 參數)。
  9. enclosing function 為 template 實例時:最多一個 variadic 參數且須在最後、template 參數須具名、實例化引數型別不可為 function-local 或 private/protected member。
  10. MSVC host 編譯器:enclosing function 須有 external linkage。
  11. MSVC:extended lambda 不可定義在 if constexpr block body 內。
  12. 捕捉限制(重要):
    • 只能 by value 捕捉,不可 by reference。
    • 陣列型別變數維度 > 7 不可捕捉;陣列元素型別須 default-constructible 且 copy-assignable。
    • variadic argument pack 的元素不可捕捉。
    • 捕捉變數型別不可 function-local(extended lambda closure 除外)或 private/protected member。
    • init-capture:host-device extended lambda 不支援;device extended lambda 支援,但 initializer 不可為陣列或 std::initializer_list
    • extended lambda 的 operator() 非 constexpr,closure type 非 literal type;不可用 constexpr/consteval
    • 巢狀於 extended lambda 內的 if constexpr block 中,變數不可首次隱式捕捉(除非已在外部隱式捕捉或在明確 capture list)。
void host_function() {
  auto l1 = [x = 1] __device__ () { return x; };           // 正確:device-only 可 init-capture
  // auto l2 = [x = 1] __host__ __device__ () { return x; }; // 錯誤:host-device 不可 init-capture
  int a = 1;
  // auto l3 = [&a] __device__ () { return a; };            // 錯誤:不可 by-reference 捕捉
}

__CUDA_ARCH__ 與 return type 內省限制

Warning

  • 13. compiler 對函式內每個 extended lambda 指派 counter;故 extended lambda 的存在與相對宣告順序不可依賴 __CUDA_ARCH__ 是否定義或其值(不可放在 #if defined(__CUDA_ARCH__) 內)。
  • 16. 傳給 __global__ 的 lambda,其 body 中捕捉變數的表達式必須與 __CUDA_ARCH__ 無關;否則 device/host 兩次編譯的 closure layout 不同,程式可能執行錯誤。
  • 14/15/17. placeholder type 在 host code 中通常不定義等價的 operator()、也不提供 pointer-to-function 轉換運算子;除非 ..._with_preserved_return_type() 為 true,否則在 host code 內省 device-only lambda 的 return/parameter type 會出錯(device code 內省則可)。
  • 18. placeholder type 可能定義特殊成員函式,使 std::is_trivially_copyable 等 trait 在 CUDA front-end 與 host 編譯器結果不一致;勿用其結果去實例化 __global__/__device__/__constant__/__managed__ template。

Note

compiler 只會對情況 1-12 的部分產生診斷;情況 13-17 不產生診斷,但 host 編譯器可能編譯失敗。

Host-Device Lambda 優化注意

*this 捕捉 by-value(C++17)

依 C++11/14 規則,lambda 引用 class member 時捕捉的是 this 指標(by value),而非 member 本身。若 extended device/host-device lambda 在 host 函式定義、於 GPU 執行,且 this 指向 host memory,存取該 member 會造成 runtime error

C++17 引入 *this 捕捉模式:複製 *this 指向的物件本身而非指標。

struct MyStruct {
  int var;
  __host__ __device__ MyStruct() : var(10) {}
  void run() {
    auto lambda1 = [=, *this] __device__ {   // 複製 *this,GPU 存取 copy_of_star_this->var
      return var + 1;
    };
    foo<<<1, 1>>>(lambda1);                  // kernel launch 成功
    cudaDeviceSynchronize();
  }
};
Note

ADL 副作用:placeholder type 的 template 引數含 enclosing function 位址,可能讓額外 namespace 參與 Argument-Dependent Lookup,導致 host 編譯器選到錯誤的 overload(產生歧義/編譯失敗)。

Polymorphic Function Wrappers function

<nvfunctional> 提供多型函式包裝器 class template nvstd::function,可儲存、複製、呼叫任意 callable target(如 lambda),host 與 device code 皆可用

#include <nvfunctional>
__global__ void kernel(int* result) {
  nvstd::function<int()> fn1 = device_function;        // __device__
  nvstd::function<int()> fn2 = host_device_function;   // __host__ __device__
  nvstd::function<int()> fn3 = [](){ return 10; };
  *result = fn1() + fn2() + fn3();
}
Warning

無效用法:

  • host code 的 nvstd::function 不可__device__ 函式位址(或 operator()__device__ 的 functor)初始化。
  • device code 的 nvstd::function 不可__host__ 函式(或 __host__ operator() functor)初始化。
  • nvstd::function 實例不可在 runtime 從 host 傳到 device(或反向)。
  • __global__ 函式從 host 啟動,其參數型別不可nvstd::function

nvstd::function 的成員(建構/解構/assignment/swap/operator bool/比較)多為 __device__ __host__,但函式呼叫運算子 operator()(ArgTypes...)__device__

考試/測驗重點

題型 關鍵答案
device printf() 回傳什麼? 解析的引數數(非字元數);無引數→0、format 為 NULL-1、內部錯誤→-2
device printf() 引數上限? 32 個(不含 format string),多出的被忽略
printf buffer 預設大小與性質? 1 MB環狀緩衝;滿了覆蓋舊輸出;cudaLimitPrintfFifoSize 調整
printf 何時 flush? kernel launch、同步、blocking memcpy、module load/unload、context 銷毀、host callback 前;程式結束不會 flush
%ld 跨平台陷阱? Windows long 32-bit、Linux 64-bit;Linux 編譯 + Windows 執行會損毀輸出
device heap 預設大小?如何改? 預設 8 MB;用 cudaDeviceSetLimit(cudaLimitMallocHeapSize, ...),須在任何配置前
device heap 大小何時不可變? module 載入 context 後不可變、也不動態調整
device malloc() 對齊? 16-byte;__nv_aligned_device_mallocalign 須非零 2 的次方
device malloc 配置可用 cudaFree 釋放嗎? 不可;device heap 與 host runtime API 記憶體互不相通
clock() 量到的週期數準嗎? 偏大;因 thread 被 time-sliced,量到值 > 實際執行指令週期
lambda 執行空間如何決定? 由最內層 enclosing function scope 決定;無 scope → __host__
哪種 lambda 可傳給 __global__ 執行空間為 __device____host__ __device__;global/namespace scope 不可
什麼是 extended lambda? 定義在 __host__/__host__ __device__ 函式內、帶執行空間標註的 lambda;需 --extended-lambda
標了 __device__ 就一定是 extended lambda 嗎? ;enclosing function 須為 __host____host__ __device____device__/__global__ 函式內、或 global/namespace scope 的標註 lambda 都不是 extended lambda
extended lambda 啟用的巨集? __CUDACC_EXTENDED_LAMBDA__
extended lambda 捕捉限制? 只能 by value;陣列維度 ≤ 7;host-device 不可 init-capture;不可捕 variadic pack 元素
為何不可依 __CUDA_ARCH__ 改變 extended lambda? compiler 用 counter 識別 lambda;device/host 兩次編譯需一致的 closure layout
為何 extended host-device lambda 在 host 端優化較差? placeholder type 以間接函式呼叫呼叫 operator(),host 編譯器難以將 lambda body inline;純 __host__ lambda 可輕易 inline,device-only lambda 不可從 host 呼叫故無此問題
*this 捕捉解決什麼問題? C++17 複製物件本身而非 this 指標,避免 GPU 上存取 host this->member 的 runtime error
nvstd::function 哪裡定義?跨邊界可傳嗎? <nvfunctional>;host/device 皆可用,但不可跨 host/device 邊界傳遞或初始化
nvstd::function::operator() 執行空間? __device__(其餘成員多為 __device__ __host__
RTTI 與例外是否能在 device code 用? 不支援(typeiddynamic_cast 等)— 見 C/C++ Language Restrictions