我通常会忽略这些类型的优化问题,因为在我看来,它们处于偏离主题的边缘。最糟糕的是,你没有提供MCVE https://stackoverflow.com/help/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% 的加速(7.1 毫秒,而同一 GTX 970 上为 1.6 毫秒)。
巨大的免责声明:您在这里看到的所有代码都是在 45 分钟午休期间编写的,并且一直如此very轻微测试。我绝对不声称这个答案中的任何内容实际上是正确的。我已经确认它可以编译,并且当我运行它来获取分析数据时不会产生运行时错误。这就对了。买者自负等等。