C代码优化,平滑功能



对于一项任务,我们被要求优化代码,以获得一个"平滑"函数,描述为:

平滑函数将源图像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; }

结构像素存储一个红色、绿色和蓝色值(整数)。函数avg返回第(i,j)个像素周围的所有像素的平均值。您的任务是优化平滑(和平均值)以尽可能快地运行。(注意:函数avg是一个局部函数,您可以完全去掉它,以其他方式实现平滑。)这个代码(以及avg的实现)在文件kernels.c.中

有人知道我如何优化这个吗?

您可以通过将矩阵/图像划分为方形瓦片并一次平滑一个瓦片来执行循环的循环平铺/循环条挖掘。这样可以实现更好的缓存利用率。

请考虑当前版本。它遍历图像,一次访问三行,写入中间一行。

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中的所有象素等。顺序并不重要,您甚至可以并行运行每个瓦片。

当然,这对于大图像和相对较大的瓦片是有利的,您可以尝试瓦片大小,例如,从横跨一个或两个缓存行的瓦片行开始。

有关此方法的更多信息,请查看循环平铺


尽管如此,值得注意的是,编译器应该自己完成这项工作。

根据编译器的优化功能,这通常会受益于标准优化,如循环展开、显式矢量化、循环阻塞,以及可能的环路交换,具体取决于图像的布局方向。这些都应该包含在你的课本或课程笔记中。如果没有,这些就是要在网上搜索的关键词。

图像平滑是结构化网格应用程序的常见示例:结构化网格

毫无疑问,您的应用程序将受益于循环展开和循环重新排序技术(尤其是循环平铺),您可以在这里学习:优化

请注意,有效优化结构化网格计算,特别是在单个时间步长上,是一项不那么琐碎的任务,人们因此获得了博士学位:Stencil probe不管怎样,你的计算相当容易,所以你应该实现显著的加速。然而,实现循环平铺可能很麻烦,在某些情况下会适得其反,您可能想尝试一个多面体编译器,如Pluto,它能够快速生成具有任意平铺维度的平铺代码。为了获得良好的性能,选择正确的瓦片尺寸是至关重要的。在当前的体系结构中,由于存在硬件预取矩形瓦片,因此效果更好:缓存优化

最新更新