在GPU編程中,精確測量代碼執行時間是性能優化的關鍵步驟。CUDA提供了專門的計時工具來幫助開發者準確獲取核函數(Kernel)、內存拷貝等操作的耗時。本文將詳細介紹CUDA計時函數的使用方法,并通過實例代碼演示如何高效測量GPU代碼的執行時間。
為什么需要CUDA計時函數?
在CPU和GPU異構計算中,CPU和GPU的工作是異步的。若使用傳統的CPU計時方法(如clock()
或std::chrono
),可能無法準確測量GPU代碼的執行時間。CUDA的事件(Event)機制能夠直接在GPU硬件層面記錄時間戳,避免了CPU-GPU同步帶來的誤差。
一、CUDA事件(Event)計時原理
CUDA事件是基于GPU內部時鐘的輕量級計時工具,原理如下:
-
事件記錄:在代碼中插入事件標記,記錄GPU執行到該點的時間戳。
-
時間差計算:通過兩個事件的時間戳差值計算代碼段的執行時間。
二、CUDA計時函數的使用步驟
1. 創建事件對象
使用cudaEventCreate
創建事件對象:
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
2. 記錄事件時間戳
在需要計時的代碼段前后插入事件記錄:
cudaEventRecord(start); // 記錄起始時間戳
// 執行需要計時的代碼(例如核函數)
myKernel<<<grid, block>>>(...);
cudaEventRecord(stop); // 記錄結束時間戳
3. 同步事件
由于GPU執行是異步的,需等待事件完成:
cudaEventSynchronize(stop); // 等待stop事件完成
4. 計算時間差(毫秒)
使用cudaEventElapsedTime
計算時間差:
float milliseconds = 0;
cudaEventElapsedTime(&milliseconds, start, stop);
printf("執行時間: %.3f ms\n", milliseconds);
5. 銷毀事件對象
釋放事件資源:
cudaEventDestroy(start);
cudaEventDestroy(stop);
三、完整示例代碼
以下代碼演示了如何測量一個核函數的執行時間:
#include <stdio.h>
#include <cuda_runtime.h>// 簡單的核函數:向量加法
__global__ void vectorAdd(int *a, int *b, int *c, int n) {int i = blockIdx.x * blockDim.x + threadIdx.x;if (i < n) {c[i] = a[i] + b[i];}
}int main() {const int N = 1 << 20; // 1M元素int *a, *b, *c;int *d_a, *d_b, *d_c;// 分配主機內存a = (int*)malloc(N * sizeof(int));b = (int*)malloc(N * sizeof(int));c = (int*)malloc(N * sizeof(int));// 分配設備內存cudaMalloc(&d_a, N * sizeof(int));cudaMalloc(&d_b, N * sizeof(int));cudaMalloc(&d_c, N * sizeof(int));// 初始化數據for (int i = 0; i < N; i++) {a[i] = i;b[i] = i * 2;}// 拷貝數據到設備cudaMemcpy(d_a, a, N * sizeof(int), cudaMemcpyHostToDevice);cudaMemcpy(d_b, b, N * sizeof(int), cudaMemcpyHostToDevice);// 創建CUDA事件cudaEvent_t start, stop;cudaEventCreate(&start);cudaEventCreate(&stop);// 定義線程塊和網格大小int blockSize = 256;int gridSize = (N + blockSize - 1) / blockSize;// 記錄起始事件cudaEventRecord(start);// 啟動核函數vectorAdd<<<gridSize, blockSize>>>(d_a, d_b, d_c, N);// 記錄結束事件cudaEventRecord(stop);cudaEventSynchronize(stop); // 等待核函數執行完成// 計算時間差float milliseconds = 0;cudaEventElapsedTime(&milliseconds, start, stop);printf("核函數執行時間: %.3f ms\n", milliseconds);// 拷貝結果回主機cudaMemcpy(c, d_c, N * sizeof(int), cudaMemcpyDeviceToHost);// 驗證結果(可選)bool success = true;for (int i = 0; i < N; i++) {if (c[i] != a[i] + b[i]) {success = false;break;}}if (success) {printf("結果驗證成功!\n");} else {printf("結果驗證失敗!\n");}// 釋放資源cudaEventDestroy(start);cudaEventDestroy(stop);cudaFree(d_a);cudaFree(d_b);cudaFree(d_c);free(a);free(b);free(c);return 0;
}
四、注意事項
-
同步的必要性:
如果未調用cudaEventSynchronize
,時間差的計算可能不準確。
核函數啟動后,CPU會繼續執行后續代碼,必須同步等待GPU完成。 -
事件的開銷:
CUDA事件的創建和銷毀有一定開銷,避免在頻繁調用的代碼段中使用。 -
多流(Stream)環境:
若使用多流并行,需為每個流單獨創建事件,并通過cudaEventRecord
指定流。 -
時間單位:
cudaEventElapsedTime
返回的時間單位為毫秒(ms),分辨率約為0.5微秒。