提高2d图像“跟踪”CUDA内核性能的技巧?

时间:2013-07-03 00:28:40

标签: performance image-processing optimization cuda

你能给我一些优化这个CUDA代码的技巧吗?

我在具有计算能力1.3的设备上运行它(我需要它用于特斯拉C1060虽然我现在在具有相同计算能力的GTX 260上进行测试)并且我有几个内核如下所示。我需要执行此内核的线程数由long SUM给出并取决于size_t Msize_t N,它们是作为参数接收的矩形图像的维度,它可能与{{50x50有很大差异。 1}}到10000x10000像素或更多。虽然我最感兴趣的是用Cuda制作更大的图像。

现在必须在所有方向和角度上跟踪每个图像,并且必须对从跟踪中提取的值进行一些计算。因此,例如,对于500x500图像,我需要229080 threads计算内核,其下面是SUM的值(这就是为什么我检查线程标识idHilo没有覆盖它的原因)。我将几个数组一个接一个地复制到设备的全局内存中,因为我需要访问它们进行全长SUM的计算。喜欢这个

cudaMemcpy(xb_cuda,xb_host,(SUM*sizeof(long)),cudaMemcpyHostToDevice);

cudaMemcpy(yb_cuda,yb_host,(SUM*sizeof(long)),cudaMemcpyHostToDevice);

...etc

因此,每个数组的每个值都可以被一个线程访问。所有都在内核调用之前完成。根据Nsight上的Cuda Profiler,对于246.016 us图像,最高的记忆持续时间为500x500,因此不会花这么长时间。

但是我在下面复制的内核对于任何实际使用花费的时间太长了(根据下面的内核的Cuda剖析器为500x500图像为3.25秒,对于具有最长持续时间的内核为5.052秒)所以我需要看看我是否可以优化它们。

我以这种方式安排数据

首先是块维度

dim3 dimBlock(256,1,1);

然后是每个网格的块数

dim3 dimGrid((SUM+255)/256);

895 blocks图像的500x500个。

我不确定如何在我的情况下使用合并和共享内存,或者即使用数据的不同部分多次调用内核也是个好主意。这些数据是彼此独立的,所以理论上我可以多次调用该内核,如果需要,可以同时调用229080个线程。

现在考虑外部for循环

for(t=15;t<=tendbegin_cuda[idHilo]-15;t++){

取决于

tendbegin_cuda[idHilo]

其值取决于每个线程,但大多数线程具有相似的值。

根据Cuda Profiler,全局存储效率为0.619,此内核的全局负载效率为0.951。其他内核具有相似的值。

这样好吗?坏?我怎样才能解释这些价值观?遗憾的是,计算能力1.3的设备没有提供其他有用的信息来评估代码,如多处理器和内核内存或指令分析。分析后得到的唯一结果是“低全局内存存储效率”和“低全局内存负载效率”,​​但我不确定如何优化这些结果。

void __global__ t21_trazo(long SUM,int cT, double Bn, size_t M, size_t N, float* imagen_cuda, double* vector_trazo_cuda, long* xb_cuda, long* yb_cuda, long* xinc_cuda, long* yinc_cuda, long* tbegin_cuda, long* tendbegin_cuda){

long xi;
long yi;
int t;
int k;
int a;
int ji;
long idHilo=blockIdx.x*blockDim.x+threadIdx.x;

int neighborhood[31];
int v=0;

if(idHilo<SUM){

    for(t=15;t<=tendbegin_cuda[idHilo]-15;t++){

        xi = xb_cuda[idHilo] + floor((double)t*xinc_cuda[idHilo]);
        yi = yb_cuda[idHilo] + floor((double)t*yinc_cuda[idHilo]);
        neighborhood[v]=floor(xi/Bn);
        ji=floor(yi/Bn);

        if(fabs((double)neighborhood[v]) < M && fabs((double)ji)<N)
        {
            if(tendbegin_cuda[idHilo]>30 && v==30){

                if(t==0)
                vector_trazo_cuda[20+idHilo*31]=0;

                for(k=1;k<=15;k++)
                vector_trazo_cuda[20+idHilo*31]=vector_trazo_cuda[20+idHilo*31]+fabs(imagen_cuda[ji*M+(neighborhood[v-(15+k)])]-
                            imagen_cuda[ji*M+(neighborhood[v-(15-k)])]);


                for(a=0;a<30;a++)
                neighborhood[a]=neighborhood[a+1];

                v=v-1;
            }

            v=v+1;
        }
    }
}

}

编辑:

更改SP触发器的DP触发器仅略微改善了持续时间。循环展开内环实际上没有帮助。

2 个答案:

答案 0 :(得分:1)

对于非结构化的答案感到抱歉,我只是会抛出一些通常有用的评论,并引用您的代码,以使其对其他人更有用。

算法更改始终是优化的第一位。有没有其他方法可以解决需要较少数学/迭代/内存等问题。

如果精度不是一个大问题,请使用浮点(或使用较新架构的半精度浮点)。当您短暂尝试时,它并没有对您的性能产​​生太大影响,部分原因是因为您仍然在浮点数据上使用双精度计算(工厂需要加倍,所以如果您使用浮点数,它会转换您的浮点数为double,双数学运算,返回double并转换为float,使用fabsf)。

如果您不需要使用浮点数的绝对全精度,请使用快速数学(编译器选项)。

乘法比除法快得多(特别是对于全精度/非快速数学)。在内核之外计算1 / var然后乘以而不是在内核中划分。

不知道它是否得到优化,但你应该使用递增和递减运算符。 V = V-1;可能是v--;等

转换为int将截断为零。 floor()将截断为负无穷大。你可能不需要显式floor(),如上所述,floorf()也可以浮动。当你将它用于整数类型的中间计算时,它们已经被截断了。因此,您无缘无故地转换为双倍并返回。使用适当类型的函数(abs,fabs,fabsf等)

if(fabs((double)neighborhood[v]) < M && fabs((double)ji)<N)
change to
if(abs(neighborhood[v]) < M && abs(ji)<N)

vector_trazo_cuda[20+idHilo*31]=vector_trazo_cuda[20+idHilo*31]+
    fabs(imagen_cuda[ji*M+(neighborhood[v-(15+k)])]-
        imagen_cuda[ji*M+(neighborhood[v-(15-k)])]);
change to 
vector_trazo_cuda[20+idHilo*31] +=
    fabsf(imagen_cuda[ji*M+(neighborhood[v-(15+k)])]-
        imagen_cuda[ji*M+(neighborhood[v-(15-k)])]);

xi = xb_cuda[idHilo] + floor((double)t*xinc_cuda[idHilo]);
change to
xi = xb_cuda[idHilo] + t*xinc_cuda[idHilo];

以上几行不必要地复杂化。从本质上讲,你这样做, 将t转换为double, 将xinc_cuda转换为double和multiply, 把它(返回双), 将xb_cuda转换为double并添加, 转换为长。

新行将在更短的时间内存储相同的结果(也更好,因为如果你超过前一种情况下的double精度,你将四舍五入到最接近的2的幂)。 此外,这四行应该在for循环之外 ...如果他们不依赖于t,你就不需要重新计算它们。如果将运行时间减少10-30倍,我不会感到惊讶。

您的结构导致大量全局内存读取,尝试从全局读取一次,在本地内存上处理计算,并向全局写入一次(如果可能的话)。

始终使用-lineinfo进行编译。使分析更容易,并且我无法评估任何开销(使用0.1到10ms执行时间范围内的内核)。

如果您计算或限制内存并相应地投入时间,请使用分析器。

尝试允许编译器尽可能使用寄存器,这是一个很大的主题。

一如既往,不要一次改变一切。我用编译/测试输入了所有这些,所以我可能会有错误。

答案 1 :(得分:0)

您可能同时运行太多线程。当你运行正确数量的线程时,似乎会出现最佳性能:足够的线程可以保持繁忙,但不会过多地分割每个并发线程可用的本地内存。

去年秋天,我建立了一个教程,用于研究使用CUDA和CUDAFY的旅行商问题(TSP)的优化。尽管问题域不同,但我从已发布的算法中实现几倍加速的步骤可能对指导您的努力很有用。该教程和代码可在CUDA Tuning with CUDAFY获得。