我通常会忽略这类优化问题,因为在我看来它们接近于离题。更糟糕的是,您没有提供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.1ms与4.5ms相比)。
但是,还有一个更加显而易见的优化。你正在执行的计算将产生对称输出矩阵。你只需要做
(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.1ms相比,现在只需要1.6ms)。
免责声明:所有您在此处看到的代码都是在45分钟的午餐时间内编写的,并且经过了非常轻微的测试。本回答中的任何内容都不作保证是正确的。我确认它可以编译并在运行时不会产生运行时错误以获取性能分析数据。仅此而已。
D
和N
的范围是什么?有哪些典型值?对于优化来说,这些事情很重要。 - Robert CrovellaN
< 30k,而D
可以是2或3。 - casparjespersenfloat2
或float3
替换内部循环 (for (int d = 0; d < D; ++d)
) ... - Lanting