[英]Optimizing execution of a CUDA kernel for Triangular Matrix calculation
我正在開發我的第一個Cuda應用程序,並且我的內核具有“低於預期的吞吐量”,這似乎是目前最大的瓶頸。
內核的任務是計算N×N大小的矩陣( DD
),其包含數據矩陣上所有元素之間的平方距離。 數據矩陣( Y
)的大小為N×D(以支持多維數據)並存儲為行主要。
資源:
__global__ void computeSquaredEuclideanDistance(const float * __restrict__ Y, float * __restrict__ DD, const int N, const int D) {
int index = blockIdx.x * blockDim.x + threadIdx.x;
int stride = blockDim.x * gridDim.x;
for (int i = index; i < N * N; i += stride) {
const int m = i / N;
const int n = i % N;
float tmp = 0;
for (int d = 0; d < D; ++d) {
const float Ynd = Y[d + D * n];
const float Ymd = Y[d + D * m];
const float Ydiff = Ynd - Ymd;
tmp += Ydiff * Ydiff;
}
DD[n + N * m] = tmp;
}
}
這是使用size_t blockSize = 256
和size_t numBlocks = (N*N + blockSize - 1)/blockSize
。
我該如何優化這個內核? 我最初的想法是,耗時的部分是在不利用某種共享內存的情況下讀取數據,但是有人能指點我如何處理這個問題嗎?
來自nvvc
分析工具的備注:
對於我的應用,典型值是:
N
<30k D
是2或3 在我看來,我通常會忽略這些類型的優化問題,因為它們處於偏離主題的邊緣。 最糟糕的是,你沒有提供MCVE,所以任何試圖回答的人都必須編寫所有自己的支持代碼來編譯和測試你的內核。 這類工作確實需要基准測試和代碼分析。 但是因為你的問題基本上是一個線性代數問題(而且我喜歡線性代數),所以我回答它而不是將其投票過於寬泛......
隨着我的胸部。 有一些東西會立即跳出代碼,這些東西可以改進,並且可能會對運行時產生重大影響。
第一個是內環的跳閘計數是先驗已知的。 任何時候你都有這樣的情況,讓編譯器知道。 循環展開和代碼重新排序是一個非常強大的編譯器優化,NVIDIA編譯器非常擅長。 如果將D移動到模板參數中,則可以執行以下操作:
template<int D>
__device__ float esum(const float *x, const float *y)
{
float val = 0.f;
#pragma unroll
for(int i=0; i<D; i++) {
float diff = x[i] - y[i];
val += diff * diff;
}
return val;
}
template<int D>
__global__
void vdistance0(const float * __restrict__ Y, float * __restrict__ DD, const int N)
{
int index = blockIdx.x * blockDim.x + threadIdx.x;
int stride = blockDim.x * gridDim.x;
for (int i = index; i < N * N; i += stride) {
const int m = i / N;
const int n = i % N;
DD[n + N * m] = esum<D>(Y + D * n, Y + D * m);
}
}
template __global__ void vdistance0<2>(const float *, float *, const int);
template __global__ void vdistance0<3>(const float *, float *, const int);
編譯器將內聯esum
並展開內部循環,然后它可以使用其重新排序啟發式來更好地交錯加載和觸發器以提高吞吐量。 生成的代碼也具有較低的寄存器占用空間。 當我在N = 10000和D = 2的情況下運行時,我的速度提高了約35%(使用CUDA 9.1的GTX 970上的速度為7.1毫秒,而4.5毫秒)。
但是有一個比這更明顯的優化。 您正在執行的計算將生成對稱輸出矩陣。 你只需要做(N*N)/2
運算就可以計算完整矩陣,而不是你在代碼中做的N*N
[技術上是N(N/2 -1)
因為對角線條目為零,但是讓為了討論的目的忘記對角線]。
因此,采用不同的方法並使用一個塊來計算上三角輸出矩陣的每一行,那么您可以執行以下操作:
struct udiag
{
float *p;
int m;
__device__ __host__ udiag(float *_p, int _m) : p(_p), m(_m) {};
__device__ __host__ float* get_row(int i) { return p + (i * (i + 1)) / 2; };
};
template<int D>
__global__
void vdistance2(const float * __restrict__ Y, float * __restrict__ DD, const int N)
{
int rowid = blockIdx.x;
int colid = threadIdx.x;
udiag m(DD, N);
for(; rowid < N; rowid += gridDim.x) {
float* p = m.get_row(rowid);
const float* y = Y + D * rowid;
for(int i=colid; i < (N-rowid); i += blockDim.x) {
p[i] = esum<D>(y, y + D * i);
}
}
}
template __global__ void vdistance2<2>(const float *, float *, const int);
template __global__ void vdistance2<3>(const float *, float *, const int);
這使用一個小輔助類來封裝上三角輸出矩陣的尋址方案所需的三角形數。 這樣做可以節省大量的內存和內存帶寬,並減少計算的總FLOP數量。 如果之后需要做其他事情,BLAS(和CUBLAS)支持上三角矩陣或下三角矩陣的計算。 使用它們。 當我運行這個時,我獲得了大約75%的加速(在相同的GTX 970上為7.1毫秒與1.6毫秒)。
巨大的免責聲明 :您在這里看到的所有代碼都是在45分鍾的午休時間內編寫的,並且經過了非常輕微的測試。 我絕對沒有聲稱這個答案中的任何內容實際上是正確的。 我已經確認它在運行它以獲取分析數據時編譯並且不會產生運行時錯誤。 這就對了。 Cavaet Emptor等等。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.