对于作业,我们被要求优化代码以获得“平滑”功能,描述如下:
平滑功能将源图像src作为输入,并将平滑后的结果返回到目标图像dst中。以下是实施的一部分:
void naive_smooth(int dim, pixel *src, pixel *dst) {
int i, j;
for(i=0; i < dim; i++)
for(j=0; j < dim; j++)
dst[RIDX(i,j,dim)] = avg(dim, i, j, src); /* Smooth the (i,j)th pixel */
return; }
struct pixel存储红色,绿色和蓝色值(整数)。 函数avg返回第(i,j)像素周围的所有像素的平均值。您的任务是优化平滑(和平均)以尽可能快地运行。 (注意:函数avg是一个局部函数,你可以完全摆脱它以其他方式实现平滑。) 此代码(以及avg的实现)位于kernels.c。
文件中任何人都知道如何优化这一点?
答案 0 :(得分:4)
您可以通过将矩阵/图像划分为方形切片并一次平滑一个切片来执行循环切片/循环条带挖掘。这样可以提高缓存利用率。
考虑当前版本。它遍历图像,一次访问三行,写入中间一行。
a[i-1][0], a[i-1][1], ..., a[i-1][dim-1]
a[i ][0], a[i ][1], ..., a[i ][dim-1]
a[i+1][0], a[i+1][1], ..., a[i+1][dim-1]
当它到达图像的最右侧时,第一列可能会从缓存中丢弃。但是当你移动到下一行时,它们将很快被需要,访问将是:
a[i ][0], a[i ][1], ..., a[i ][dim-1]
a[i+1][0], a[i+1][1], ..., a[i+1][dim-1]
a[i+2][0], a[i+2][1], ..., a[i+2][dim-1]
相反,您可以在图块中处理图像,例如:
a[i ][B], a[i ][B+1], ..., a[i ][B+B-1]
a[i+1][B], a[i+1][B+1], ..., a[i+1][B+B-1]
a[i+2][B], a[i+2][B+1], ..., a[i+2][B+B-1]
其中B是图块大小。
或者用图片,使其更清晰:
000111222
000111222
000111222
333444555
333444555
333444555
666777888
666777888
666777888
这里我们有一个9x9图像,分为9个图块,编号从0到8,你的目标是以这样的方式编写循环:首先平滑图块0中的所有像素,然后是图块1中的所有像素,然后是图块2中的所有像素,等等。顺序并不重要,您甚至可以并行运行每个图块。
当然,这对于大图像和相对较大的图块都是有利的,您可以尝试使用图块大小,例如,从跨越一个或两个缓存行的图块行开始。
有关此方法的更多信息,请查看Loop tiling
尽管如此,值得注意的是你的编译器本身应该这样做。
答案 1 :(得分:2)
根据编译器的优化可提供的内容,这通常会受益于标准优化,例如循环展开,显式矢量化,循环阻塞,以及可能的循环交换,具体取决于图像布局的方向。这些都应该包含在您的教科书或课程笔记中。如果没有,那些就是在线搜索的关键词。
答案 2 :(得分:1)
图像平滑是结构化网格应用程序的常见示例:Structured Grids
您的应用程序肯定会受益于循环展开和循环重新排序技术(尤其是循环平铺),您可以在此处学习:Optimizations
请注意,有效地优化结构化网格计算,尤其是在单个时间步骤上,这并不是一件轻而易举的事情,人们可以获得博士学位:Stencil probe 无论如何,您的计算相当容易,因此您应该实现显着的加速。 但是,实现循环切片可能很麻烦,并且在某些情况下会产生反作用,您可能需要尝试使用多面体编译器,例如Pluto,它能够快速生成具有任意切片尺寸的平铺代码。 选择正确的图块尺寸是获得良好性能的基础,在当前架构中,由于硬件预取矩形图块的存在效果更好:Cache optimizations