2

对于一项任务,我们被要求优化代码以实现“平滑”功能,描述为:

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

任何人都知道如何优化这个有一些建议吗?

4

3 回答 3

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 中的所有像素,然后是所有像素tile 2 中的像素等。顺序并不重要,您甚至可以并行运行每个 tile。

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

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


尽管如此,值得注意的是你的编译器应该自己做这件事。

于 2012-12-05T22:00:26.310 回答
2

根据您的编译器的优化可以提供什么,这通常会受益于标准优化,例如循环展开、显式矢量化、循环阻塞以及可能的循环交换,具体取决于图像的布局方向。这些都应该包含在您的教科书或课程笔记中。如果没有,这些是在线搜索的关键字。

于 2012-12-05T21:54:35.217 回答
1

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

您的应用程序肯定会受益于循环展开和循环重新排序技术(尤其是循环平铺),您可以在此处学习:优化

请注意,有效地优化结构化网格计算,尤其是在单个时间步上是一项不那么简单的任务,人们为此获得了博士学位:Stencil probe 无论如何,您的计算相当容易,因此您应该实现显着的加速。但是,实现循环平铺可能很麻烦,并且在某些情况下会适得其反,您可能想尝试使用Pluto等多面体编译器,它能够快速生成具有任意平铺尺寸的平铺代码。选择正确的切片尺寸是实现良好性能的基础,在当前架构中,由于硬件预取矩形切片的存在效果更好:缓存优化

于 2012-12-05T22:03:57.720 回答