GPU應用性能往往受限於內存帶寬,優化全局內存訪問是獲取高性能的首要任務。本文系統梳理對齊訪問、合併訪問、緩存層級、讀寫模式、數據佈局(AoS vs SoA)、循環展開等核心優化技術,並配合實測數據驗證。
| 概念 | 定義 | 效果 |
|---|---|---|
| Aligned Access | 訪問地址為緩存行倍數(128B對齊) | 避免跨緩存行訪問導致帶寬浪費 |
| Coalesced Access | Warp 32線程訪問連續內存塊 | 單次事務滿足所有請求,吞吐最大化 |
理想模式:連續且對齊 → 單次128B傳輸,總線利用率100%
最差模式:離散非對齊 → 多次傳輸,大量無效數據,利用率極低
注意:GPU L1僅針對空間局部性優化,無時間局部性,頻繁訪問不會駐留數據。
__ldg(&ptr)內建函數 或 const __restrict__修飾指針__global__ void readOffset(float *A, float *B, float *C, int n, int offset) {
unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
unsigned int k = i + offset;
if (k < n) C[i] = A[k] + B[k];
}
| Offset | L1啟用 gld_efficiency | L1禁用 gld_efficiency | L1啟用 gld_transactions | L1禁用 gld_transactions |
|---|---|---|---|---|
| 0 (對齊) | 100% | 100% | 65184 | ~65744 |
| 11 (未對齊) | 49.81% | 80% | 131039 | ~65744 |
| 128 (對齊) | 100% | 100% | 65744 | ~65744 |
未對齊導致L1事務數翻倍,禁用L1後事務數恢復正常但延遲略增。
// AoS: 結構體數組
struct InnerStruct { float x, y; };
InnerStruct myAoS[N]; // x,y交替存儲
// SoA: 數組結構體
struct InnerArray { float x[N], y[N]; }; // 所有x連續,所有y連續
實測對比(Fermi M2070,1M元素):
| 佈局 | 耗時 | gld_efficiency | gst_efficiency |
|---|---|---|---|
| AoS | 0.286ms | 50% | 50% |
| SoA | 0.200ms | 100% | 100% |
SoA預先連續佈局,完美滿足合併訪問,性能提升43%。
每線程處理4個元素,增加獨立內存操作並行度:
__global__ void readOffsetUnroll4(...) {
unsigned int i = blockIdx.x * blockDim.x * 4 + threadIdx.x;
// 處理 i, i+blockDim.x, i+2*blockDim.x, i+3*blockDim.x
}
實測:展開後耗時從1.8ms降至0.6ms(3倍提升),對齊/未對齊差異縮小但仍存在。
調整Grid/Block配置,目標佔用率最大化:
| Block Size | Grid Size | 耗時 | 理論佔用率 |
|---|---|---|---|
| 1024 | 1024 | 0.169ms | 較低 |
| 512 | 2048 | 0.159ms | 中等 |
| 256 | 4096 | 0.157ms | 最佳(48 warps/SM) |
| 128 | 8192 | 0.158ms | 次優(32 warps/SM) |
256線程/塊達到Fermi架構下最佳佔用率(8塊×4 warps=32 warps,接近48上限)。
參考書:《Professional CUDA C Programming》