理解 GPU 硬體執行模型是編寫高效能 CUDA 核函數的前提。本文深入剖析 Warp 執行機制、分歧問題、資源分區限制、延遲隱藏理論、佔用率計算與同步原語,並提供實測代碼與優化策略。
控制流分支(if/else、循環)導致同一 Warp 內線程走不同路徑 → 序列化執行各分支 → 有效吞吐率下降。
// 原始:依 threadIdx.x 奇偶分支 → 嚴重分歧
__global__ void mathKernel1(float *c) {
int tid = blockIdx.x * blockDim.x + threadIdx.x;
float a = 0, b = 0;
if (tid % 2 == 0) a = 100;
else b = 200;
c[tid] = a + b;
}
// 優化:依 Warp 整體分支 → 無分歧
__global__ void mathKernel2(float *c) {
int tid = blockIdx.x * blockDim.x + threadIdx.x;
int warpId = tid / warpSize;
float a = 0, b = 0;
if (warpId % 2 == 0) a = 100;
else b = 200;
c[tid] = a + b;
}
簡單分支經編譯器轉為「預測指令」(Predicate Instruction),無分歧開銷。複雜分支才觸發真正分歧。可用 nvprof --metrics branch_efficiency 量化。
Max Blocks/SM = min(
Registers/SM / (Regs/Thread * Threads/Block),
SharedMem/SM / SharedMem/Block,
MaxBlocks/SM, MaxWarps/SM / (Threads/Block / 32)
)
資源不足導致 Kernel 啟動失敗(cudaErrorInvalidConfiguration)。
調度器每週期從 Eligible 中選擇 → 最大化指令級並行(ILP)。
Fermi (CC 2.0):FP32 吞吐 640 ops/SM/cycle → 需 640/32 = 20 Warps 全滿載。
Fermi:記憶體頻寬 144 GB/s → 換算每 SM 每週期 ~74 bytes → 每線程 4 bytes (float) → 需 18500 線程 ≈ 579 Warps → 每 SM 36 Warps。
結論:記憶體密集型核函數需更高佔用率;計算密集型相對寬鬆。
Occupancy = Active Warps/SM / Max Warps/SM
nvprof --metrics achieved_occupancy 對比理論值。警示:佔用率非唯一指標,達到閾值(~75%)後,指令吞吐、記憶體頻寬、快取命中率等成為新瓶頸。
cudaDeviceSynchronize():Host 等待 Device 所有任務完成(同步點)。__syncthreads():Block 內所有線程屏障同步,配合共享記憶體交換資料。nsight compute 分析 Warp 分歧、佔用率、記憶體吞吐。launch_bounds、避免過度展開。cudaMemcpyAsync + 多 Stream 重疊計算與傳輸。Warp 是 GPU 並行的原子單位。掌握分歧規避、資源平衡、延遲隱藏數學模型,並善用分析工具量化指標,才能將理論峰值效能轉化為實際應用吞吐。下一篇將探討記憶體層級優化(合併存取、快取策略、非同步管線)。