课程实验报告
课 程 名 称:
实验项目名称: perflab——代码性能优化
计算机组成与结构
专 业 班 级:
姓
学
名:
号:
保密 1301
杜佳卉
201308060206
指 导 教 师:
完 成 时 间: 2015 年 5 月 22 日
计算机科学与工程系
实验题目:perflab——代码性能优化
实验目的:兼顾算法原型的理解,学会以底层的角度对抽象计算作最大限度的优化。
实验环境:个人电脑,ubuntu
实验内容及操作步骤:
1. rotate 优化过程
Naive_rotate 分析
void naive_rotate(int dim, pixel *src, pixel *dst)
{
int i, j;
for (i = 0; i < dimi; i++)
for (j = 0; j < dim; j++)
dst[RIDX(dim-1-j, i, dim)] = src[RIDX(i, j, dim)];
}这个函数虽然从功能上完成了对一个像素点的 90 度转移,但实际的运行
CPE 却很低,特别在输入维度规模(dim)扩大时,也随之及剧增长。
低效的原因大致为一下几点:程序的局部性差;循环的步进值仅为 1,太小
了,使整个运行的次数较多;每次内循环中都需重复计算源像素及目标像
素的地址。
但反过来,程序的书写风格却是最与算法原理与编写抽象思维最贴近的。就
是说,高级语言经过编译器编译后得到的机器指令往往并不是最高效的,需
要通过适当的转化、变形、再设计达到机器性能的提升。
改进 1:
这是第一个改进版本。考虑到原函数的局部性较低,及循环展开次数多的问题,我将 90
度翻转的像素点作了一个 4*4 块的分割。这样对于 Cache 的命中角度而言有所帮助。
运行后的结果如下:
性能较之前的在大规模输入下有了明显的提高。但还具有进一步提升的可能。
改进 2:
顺着之前的思路,将循环的步长改为 32,作一个 32*32 块的分割,运行的结果
如下:
但之后再进一步改用 64*64 分块的时候遇到了如下的错误。
因此我们可以看到如果要能充分使用到 Cache 时,选择 32*32 的分块决策可能
是最优的。
改进 3:
根据改进 2,可知 Cache 的分块极限情况。而这一次改进回到分块的原点——4*4
分块并尝试新的循环展开方式优化。同时将取地址计算时的相同计算提取到内循环之
外。并用统一的指针进行循环遍历。
这样处理之后的运行效率提升如下:
改进 3 不如改进 2 的提升幅度大,但与改进 1 相比性能有所改善,分析可能的原
因,主要为:
a) 这个版本为 4*4 分块,虽然在地址计算及并行展开上有小幅提升,但整体效率仍
被分块化所抵消。(与 32*32 unrolled 方式相比)
b) 与改进 1 的 4*4 相比,改善点在于将原来的循环步长加 4,循环展开为了内循环
中的 4 次像素转移,更充分地利用了 CPU 的资源。
因此下一步的目标在于,将程序变为 32*32 分块模式,并用内循环 32 位展开的方式进
行处理。 (时间来不及了没有做)
2. Smooth 优化过程
Naive Smooth 分析
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);
}
经过 rotate 优化的训练后,对于性能优化有了初步的经验。因此在分析第二个
函数时,直接沿用第一个优化结果的分析思路,可知该段程序的效率不足点在于:
a) 循环并未分块,如何分块比较有效?
b) 函数调用过多。Avg 函数中又包括了其他几个辅助函数。
c) 循环内部并未展开,未利用流水线特性及局部性作 cache 层次的优化
d) 内循环寻址计算重复,并未用指针直接增减。
e) 算法计算并未最优化,Avg 函数中的循环判断套用了大量 max,min 宏定义。
Sum 函数累加的效率很低。
介于这五点,选取部分作优化处理。
改进 1:
此次优化聚焦于两点:减少甚至去除子函数调用;算法计算流程的优化。
对于算法计算流程的优化,我们分析原本的算法公式可知。整个图片的像素柔滑
处理,相当于对每个像素点做了个领域内的平均值化。
待修改版本中的求和函数先暂且不考虑,对于 avg 子函数而言:
static pixel avg(int dim, int i, int j, pixel *src)
{
int ii, jj;
pixel_sum sum;
pixel current_pixel;
initialize_pixel_sum(&sum);
for(ii = max(i-1, 0); ii <= min(i+1, dim-1); ii++)
for(jj = max(j-1, 0); jj <= min(j+1, dim-1); jj++)
accumulate_sum(&sum, src[RIDX(ii, jj, dim)]);
assign_sum_to_pixel(¤t_pixel, sum);
return current_pixel;
}
程序在边界判定上花了很多功夫,像素循环时利用边界判断,避免了顶点、第一
行、第一列及中间区域的不同处理问题。故接下来我们可以考虑如何能够更加斩
草除根低克服这一性能瓶颈,如何通过一种划分手段做到更好的提升。
我们可以将原始图像的四个端点独立出来,因为它们的领域内只有 4 个点。而
剩下的第一行与第一列像素点则在领域内有 6 个像素点。再剩下的中间区域则
为 9 个点。总结而言,可以大致分为三类:中间区域,4 个端点,4 条最外层
的边。针对这 3 种不同的情况,可以分别用三种不同的计算模式进行分析。
这样做的好处不仅解放了边界判断的冗余效率,又可以无需 pixel_sum 函数的
调用。
直接在求取平均值的时候,除以数字 9 、4 、6。
由此便诞生了第一个修改版本,其执行效率如下:
改进 2:
在这个版本中,我们进一步去除了所有使用的子函数,并重写了 sum 取和方法,将待
翻转图像分成了 3 类进行优化。代码如下:
void smooth_v2(int dim, pixel *src, pixel *dst)
{
int i,j;
pixel_sum sum;
pixel *p_s=src;
pixel *p_d=dst;
pixel *op;
//the 1st row
// (0,0) pixel
{
op=p_s;
initialize_pixel_sum(&sum);
sum.red += (int) op->red;
sum.green += (int) op->green;
sum.blue += (int) op->blue;
op++;
sum.red += (int) op->red;
sum.green += (int) op->green;
sum.blue += (int) op->blue;
op+=dim;
sum.red += (int) op->red;
sum.green += (int) op->green;
sum.blue += (int) op->blue;
op--;
sum.red += (int) op->red;
sum.green += (int) op->green;
sum.blue += (int) op->blue;
op=p_d;
op->red = (unsigned short) (sum.red/4);
op->green = (unsigned short) (sum.green/4);
op->blue = (unsigned short) (sum.blue/4);
p_d++;
}
//pixels from (1,1) to (1,dim-2)
for (j = 1; j red;
sum.green += (int) op->green;
sum.blue += (int) op->blue;
op++;
sum.red += (int) op->red;
sum.green += (int) op->green;
sum.blue += (int) op->blue;
op++;
sum.red += (int) op->red;
sum.green += (int) op->green;
sum.blue += (int) op->blue;
op+=dim;
sum.red += (int) op->red;
sum.green += (int) op->green;
sum.blue += (int) op->blue;
op--;
sum.red += (int) op->red;
sum.green += (int) op->green;
sum.blue += (int) op->blue;
op--;
sum.red += (int) op->red;
sum.green += (int) op->green;
sum.blue += (int) op->blue;
op=p_d;
op->red = (unsigned short) (sum.red/6);
op->green = (unsigned short) (sum.green/6);
op->blue = (unsigned short) (sum.blue/6);
p_s++;
p_d++;
}
{
//pixel (1,dim-1)
op=p_s;
initialize_pixel_sum(&sum);
sum.red += (int) op->red;
sum.green += (int) op->green;