计组实验4报告.docx
《计组实验4报告.docx》由会员分享,可在线阅读,更多相关《计组实验4报告.docx(13页珍藏版)》请在冰豆网上搜索。
计组实验4报告
课程实验报告
课程名称:
计算机组成与结构
实验项目名称:
perflab-handout
专业班级:
计科1403
姓名:
学号:
指导教师:
黄丽达
完成时间:
2016年5月28日
信息科学与工程学院
实验题目:
perflab-handout
实验目的:
本次实验的核心任务是要求修改kernel.c文件,并可以在使用driver进行评测时获得更好的加速比。
kernel.c文件中主要有两个需要进行优化的函数:
rotate和smooth,并分别给出了naive_rotate和naive_smooth两个函数的基本实现作为baseline作为你改进后的程序的比较对象。
本次实验,要求针对每个函数、每个人均至少写出3种优化版本、并根据driver报告的结果进行性能分析。
实验环境:
联想ThinkPadE545,Ubuntu14(32位)gdb工具
实验内容及操作步骤:
rotate原始代码分析:
voidnaive_rotate(intdim,pixel*src,pixel*dst)
{
inti,j;
for(i=0;ifor(j=0;jdst[RIDX(dim-1-j,i,dim)]=src[RIDX(i,j,dim)];
}
首先要理解RIDX的意思,在头文件defs.h中宏定义了#defineRIDX(i,j,n)((i)*(n)+(j),意思是一个n*n的二维数组的第i行,第j列。
所以这段代码的是把一幅画进行逆时针方向旋转90°该函数将所有的像素进行了行列调位、导致整幅图画进行了逆时针90°旋转。
分析代码性能不好的因素:
原代码由于使用了两重for循环,而且每一重for循环的循环次数为dim次,循环次数过多导致程序的性能非常差,优化方法一是减少循环次数,采用循环展开可以减少循环的次数达到优化效果。
优化1:
voidrotate(intdim,pixel*src,pixel*dst)
{
inti,j;
for(i=0;ifor(j=0;j{
dst[RIDX(dim-1-j,i,dim)]=src[RIDX(i,j,dim)];
dst[RIDX(dim-1-j,i+1,dim)]=src[RIDX(i+1,j,dim)];
}
for(;i{
dst[RIDX(dim-1-j,i,dim)]=src[RIDX(i,j,dim)];
}
}
运行结果截图:
从上述截图可以看出,当将循环展开成每次做两个像素点的旋转时,程序的性能明显提高,原来代码的加速比是1.3,优化后性能是原始代码的两倍,所以循环展开可以提高程序的性能。
优化2:
循环分块与写优先操作结合可以提高性能,原代码因为循环步长太长,以至于cache的命中率非常低,所以总体运算效率不高。
因此,我考虑到cache的大小,应在存储的时候进行32个像素依次存储(列存储)。
(32个像素排列是为了充分利用一级缓存(32KB),采用分块策略,每一个块大小为32)这样可以做到cache友好、可以大幅度提高效率,同时写优先操作(写地址连续)比原代码的读优先操作(读地址连续)性能会更好。
voidrotate(intdim,pixel*src,pixel*dst)
{
inti,j,i1,j1,im,jm;
intblock=32;//blockingtheMatrix
for(i=0;ifor(j=0;j{
//block*blockminimatrix
im=i+block;
for(i1=i;i1
jm=j+block;
for(j1=j;j1dst[RIDX(i1,j1,dim)]=src[RIDX(j1,dim-i1-1,dim)];
}
}
}
从上述截图可以看出,当将循环步长分成最大为32,优先dest的写操作时,程序的性能明显提高,原来代码的加速比是1.3,优化后性能是原始代码的三倍多,所以循环分块与写优先操作结合可以提高性能。
优化3:
循环展开类循环分块结合,通过减少每次迭代计算元素的数量,减少循环的迭代次数,同时分块可以逐点赋值—>逐行(列)赋值充分利用块空间,防止前面开辟的块还没有完全利用就被后来的覆盖,提高cache命中率。
voidrotate(intdim,pixel*src,pixel*dst)
{
inti,j;
intdst_base=(dim-1)*dim;
dst+=dst_base;
for(i=0;ifor(j=0;j*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src+=dim;dst++;
*dst=*src;src++;
src-=(dim<<5)-dim;
dst-=31+dim;
}
dst+=dst_base+dim;
dst+=32;
src+=(dim<<5)-dim;
}
}
从上述截图可以看出,当将循环展开32次,同时进行类分块时,程序的性能明显提高,原来代码的加速比是1.8,优化后性能是原始代码的四倍多,所以循环展开类循环分块结合可以大大提高程序性能。
smooth原始代码分析:
voidnaive_smooth(intdim,pixel*src,pixel*dst)
{
inti,j;
for(i=0;ifor(j=0;jdst[RIDX(i,j,dim)]=avg(dim,i,j,src);
}
这段代码的是把一幅画复制一份赝品,赝品的某点的像素为对应原画的周围像素的平均值(如果该像素点在四个顶点,则赝品的该点像素等于原画包括该点的周围四个点的像素平均,如果该像素点为四条边界上的像素点,则赝品的该点像素点等于原画包括该点的周围六个点的像素平均值,如果该像素点为中间点,则该点像素为原画的周围九个点的平均值)。
分析代码性能不好的因素:
这段代码频繁地调用avg函数,并且avg函数中也频繁调用initialize_pixel_sum 、accumulate_sum、assign_sum_to_pixel这几个函数,且又含有2层for循环,而我们应该减少函数调用的时间开销。
所以,需要改写代码,不调用avg函数。
优化1:
不调用avg函数,把avg函数写进smooth函数主体中,通过减少函数调用来达到优化程序的目的。
voidsmooth(intdim,pixel*src,pixel*dst)
{
inti,j;
for(i=0;i{
for(j=0;j{
intii,jj;
pixel_sumsum;
pixelcurrent_pixel;
sum.red=sum.green=sum.blue=0;
sum.num=0;
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++)
{
pixelp=src[RIDX(ii,jj,dim)];
sum.red+=(int)p.red;
sum.green+=(int)p.green;
sum.blue+=(int)p.blue;
sum.num++;
}
}
current_pixel.red=(unsignedshort)(sum.red/sum.num);
current_pixel.green=(unsignedshort)(sum.green/sum.num);
current_pixel.blue=(unsignedshort)(sum.blue/sum.num);
dst[RIDX(i,j,dim)]=current_pixel;
}
}
}
从上述运行截图可以看出,减少函数调用确实起到了优化代码的作用,但是优化效果不明显,可能是编译器在编译的时候做了优化的缘故所以人为的优化性能提升不多,但是减少函数调用确实可以达到优化代码的效果。
优化2:
对图片进行分块处理,像素点分成图片四个角、图片四条边、图片内部三块分别进行处理。
对角而言只需要4个像素点的均值,对于边而言为6个像素点均值,图片内部则需要9个像素点均值。
这样可以减少循环判断的次数,而且原代码的avg函数是在一次循环中只进行一个像素点的累加,avg函数里用了两重循环,而函数主体有有两重循环,相当于原代码用了四重循环,所以性能很差,所以改进后不使用avg函数,并且通过分块,将每个分块周围的像素点一次性加起来而不是一个循环累加一个像素点,达到大大减少循环调用的功能。
voidsmooth(intdim,pixel*src,pixel*dst)
{
inti,j,lastr,lastb,lastg;
introw=dim;
intcurr;
//四个角处理
dst[0].red=(src[0].red+src[1].red+src[dim].red+src[dim+1].red)>>2;//用">>"代替"/4"节省时间
dst[0].blue=(src[0].blue+src[1].blue+src[dim].blue+src[dim+1].blue)>>2;
dst[0].green=(src[0].green+src[1].green+src[dim].green+src[dim+1].green)>>2;
dst[dim-1].red=(src[dim-1].red+src[dim-2].red+src[dim*2-2].red+src[dim*2-1].red)>>2;
dst[dim-1].blue=(src[dim-1].blue+src[dim-2].blue+src[dim*2-2].blue+src[dim*2-1].blue)>>2;
dst[dim-1].green=(src[dim-1].green+src[dim-2].green+src[dim*2-2].green+src[dim*2-1].green)>>2;
dst[(dim-1)*dim].red=(src[(dim-1)*dim].red+src[(dim-1)*dim+1].red+src[(dim-2)*dim].red+src[(dim-2)*dim+1].red)>>2;
dst[(dim-1)*dim].blue=(src[(dim-1)*dim].blue+src[(dim-1)*dim+1].blue+src[(dim-2)*dim].blue+src[(dim-2)*dim+1].blue)>>2;
dst[(dim-1)*dim].green=(src[(dim-1)*dim].green+src[(dim-1)*dim+1].green+src[(dim-2)*dim].green+src[(dim-2)*dim+1].green)>>2;
dst[dim*dim-1].red=(src[dim*dim-1].red+src[dim*dim-2].red+src[(dim-1)*dim-1].red+src[(dim-1)*dim-2].red)>>2;
dst[dim*dim-1].blue=(src[dim*dim-1].blue+src[dim*dim-2].blue+src[(dim-1)*dim-1].blue+src[(dim-1)*dim-2].blue)>>2;
dst[dim*dim-1].green=(src[dim*dim-1].green+src[dim*dim-2].green+src[(dim-1)*dim-1].green+src[(dim-1)*dim-2].green)>>2;
//四条边
for(j=1;jdst[j].red=(src[j].red+src[j-1].red+src[j+1].red+src[j+dim].red+src[j+1+dim].red+src[j-1+dim].red)/6;
dst[j].green=(src[j].green+src[j-1].green+src[j+1].green+src[j+dim].green+src[j+1+dim].green+src[j-1+dim].green)/6;
dst[j].blue=(src[j].blue+src[j-1].blue+src[j+1].blue+src[j+dim].blue+src[j+1+dim].blue+src[j-1+dim].blue)/6;
}
for(j=dim*(dim-1)+1;jdst[j].red=(src[j].red+src[j-1].red+src[j+1].red+src[j-dim].red+src[j+1-dim].red+src[j-1-dim].red)/6;
dst[j].green=(src[j].green+src[j-1].green+src[j+1].green+src[j-dim].green+src[j+1-dim].green+src[j-1-dim].green)/6;
dst[j].blue=(src[j].blue+src[j-1].blue+src[j+1].blue+src[j-dim].blue+src[j+1-dim].blue+src[j-1-dim].blue)/6;
}
for(i=dim;idst[i].red=(src[i].red+src[i-dim].red+src[i+1].red+src[i+dim].red+src[i+1+dim].red+src[i-dim+1].red)/6;
dst[i].green=(src[i].green+src[i-dim].green+src[i+1].green+src[i+dim].green+src[i+1+dim].green+src[i-dim+1].green)/6;
dst[i].blue=(src[i].blue+src[i-dim].blue+src[i+1].blue+src[i+dim].blue+src[i+1+dim].blue+src[i-dim+1].blue)/6;
}
for(i=dim+dim-1;idst[i].red=(src[i].red+src[i-1].red+src[i-dim].red+src[i+dim].red+src[i-dim-1].red+src[i-1+dim].red)/6;
dst[i].green=(src[i].green+src[i-1].green+src[i-dim].green+src[i+dim].green+src[i-dim-1].green+src[i-1+dim].green)/6;
dst[i].blue=(src[i].blue+src[i-1].blue+src[i-dim].blue+src[i+dim].blue+src[i-dim-1].blue+src[i-1+dim].blue)/6;
}
//图片内部for(i=1;ilastr=src[row-dim].red+src[row-dim+1].red+src[row-dim+2].red+src[row].red+src[row+1].red+src[row+2].red+src[row+dim].red+src[row+dim+1].red+src[row+dim+2].red;
lastb=src[row-dim].blue+src[row-dim+1].blue+src[row-dim+2].blue+src[row].blue+src[row+1].blue+src[row+2].blue+src[row+dim].blue+src[row+dim+1].blue+src[row+dim+2].blue;
lastg=src[row-dim].green+src[row-dim+1].green+src[row-dim+2].green+src[row].green+src[row+1].green+src[row+2].green+src[row+dim].green+src[row+dim+1].green+src[row+dim+2].green;
dst[row+1].red=lastr/9;
dst[row+1].blue=lastb/9;
dst[row+1].green=lastg/9;
for(j=2;jcurr=row+j;
lastr=lastr-src[curr-dim-2].red+src[curr-dim+1].red-src[curr-2].red+src[curr+1].red-src[curr+dim-2].red+src[curr+dim+1].red;lastb=lastb-src[curr-dim-2].blue+src[curr-dim+1].blue-src[curr-2].blue+src[curr+1].blue-src[curr+dim-2].blue+src[curr+dim+1].blue;lastg=lastg-src[curr-dim-2].green+src[curr-dim+1].green-src[curr-2].green+src[curr+1].green-src[curr+dim-2].green+src[curr+dim+1].green;
dst[curr].red=lastr/9;
dst[curr].blue=lastb/9;
dst[curr].green=lastg/9;//内部其他点参考该行前一个点的像素值得到结果
}
row+=dim;
}
}
上述代码之所以使用lastr,lastb,lastg,是因为像素点的和加起来对于原来的unsignedshort类型会发生溢出,所以先定义几个int类型的变量保存像素和结果,将和平均后再赋值给每个像素点。
由运行截图可以看出,分块后程序性能大大提高,由原来的加速比为6.8提升到加速比为29.3,优化程度很高。
这个方法不但是进行了分块处理,同时也减少了循环的调用,优化结果非常可观。
优化3:
使用循环展开,减少循环次数达到优化效果。
voidsmooth(intdim,pixel*src,pixel*dst)
{
inti,j;
for(i=0;ifor(j=0;j{
dst[RIDX(i,j,dim)]=avg(dim,i,j,src);
dst[RIDX(i,j+1,dim)]=avg(dim,i,j+1,src);
}
for(;jdst[RIDX(i,j,dim)]=avg(dim,i,j,src);
}
从运行截图上可以看出,减少循环次数,将循环展开,尽管跟原代码差别不是很大,但是还是有了一些优化,尽管优化效果不够明显,但是这个方法是有效的。
实验结果及分析:
从上述运行结果对比来看,每个修改代码或多或少都得到了优化,各种方法的优化程度不一,有些方法优化效果非常显著,有些优化效果很小,但是每一个修改的代码都达到了优化的目的,所以这次实验室成功的。
实
验成绩