1. Naive_rotate
1.1原始代码分析
/*
*naive_rotate - The naive baseline version of rotate
*/
char naive_rotate_descr[] ="naive_rotate: Naive baseline implementation";
void naive_rotate(int dim, pixel *src,pixel *dst)
{
int i, j;
for (i = 0; i < dim; i++)
for(j = 0; j < dim; j++)
dst[RIDX(dim-1-j, i, dim)] = src[RIDX(i, j,dim)];
}
一开始一直不明白RIDX是啥意思,后来在头文件defs.h中找到了宏定义:
#defineRIDX(i,j,n) ((i)*(n)+(j))
那么这段代码就很容易理解了。可以理解为一幅画的旋转,它将将所有的像素进行行列调位、导致整幅图画进行了90度旋转。
然而由于这串代码的步长过长,以至于cache的命中率非常低,所以总体运算效率不高。因此,我们考虑到cache的大小,应在存储的时候进行32个像素依次存储(列存储)。(32个像素排列是为了充分利用一级缓存(32KB), 采用分块策略, 每一个块大小为32)
这样可以做到cache友好、可以大幅度提高效率。
1.2优化尝试1
首先,我考虑分块的方式,进行优化。将整个程序分成4*4的小块,提高空间局部性
char rotate_descr[] = "rotate: Currentworking version";
void rotate(int dim, pixel *src, pixel*dst)
{
int i,j,i1,j1;
for(i1=0;i1
测试的CPE
原来的代码平均加速比是4.8,而分块后代码的平均的加速比是7.0,尤其是在画的像素大小比较大的时候,在上图中dim为1024的时候加速比对比很明显!而在像素比较小的时候,反而减慢速度了(在dim=64的时候)。不过也很容易理解,当dim比较小的时候,整个画的元素都能装进高速缓存中,因此算法的优劣性就取决于算法的复杂度了。而分块的算法较原来的算法较复杂些,所以最后速度也慢了一点。
然后,我尝试将其分成8*8的小块。
发现,其速度提高并不是很明显。
最高应该是32*32.因为dim均为32的倍数,若比32大,则算法会出错。
1.2优化尝试2
采用循环展开
char rotate_descr[] = "rotate: Currentworking version,using pointer rather than computing address";
void rotate(int dim, pixel *src, pixel*dst)
{
int i;
int j;
int tmp1=dim*dim;
int tmp2=dim *31;
int tmp3=tmp1-dim;
int tmp4=tmp1+32;
int tmp5=dim+31;
dst+=tmp3;
for(i=0; i< dim; i+=32)
{
for(j=0;j
采用循环展开,优化到12.6
1.3优化尝试3
考虑矩形分块32*1,32路循环展开,并使dest地址连续,以减少存储器写次数
#define COPY(d,s) *(d)=*(s)
char rotate_descr[] = "rotate: Currentworking version";
void rotate( int dim,pixel *src,pixel *dst)
{
int i,j;
for(i=0;i=0;j-=1)
{
pixel*dptr=dst+RIDX(dim-1-j,i,dim);
pixel*sptr=src+RIDX(i,j,dim);
COPY(dptr,sptr);sptr+=dim;
COPY(dptr+1,sptr);sptr+=dim;
COPY(dptr+2,sptr);sptr+=dim;
COPY(dptr+3,sptr);sptr+=dim;
COPY(dptr+4,sptr);sptr+=dim;
COPY(dptr+5,sptr);sptr+=dim;
COPY(dptr+6,sptr);sptr+=dim;
COPY(dptr+7,sptr);sptr+=dim;
COPY(dptr+8,sptr);sptr+=dim;
COPY(dptr+9,sptr);sptr+=dim;
COPY(dptr+10,sptr);sptr+=dim;
COPY(dptr+11,sptr);sptr+=dim;
COPY(dptr+12,sptr);sptr+=dim;
COPY(dptr+13,sptr);sptr+=dim;
COPY(dptr+14,sptr);sptr+=dim;
COPY(dptr+15,sptr);sptr+=dim;
COPY(dptr+16,sptr);sptr+=dim;
COPY(dptr+17,sptr);sptr+=dim;
COPY(dptr+18,sptr);sptr+=dim;
COPY(dptr+19,sptr);sptr+=dim;
COPY(dptr+20,sptr);sptr+=dim;
COPY(dptr+21,sptr);sptr+=dim;
COPY(dptr+22,sptr);sptr+=dim;
COPY(dptr+23,sptr);sptr+=dim;
COPY(dptr+24,sptr);sptr+=dim;
COPY(dptr+25,sptr);sptr+=dim;
COPY(dptr+26,sptr);sptr+=dim;
COPY(dptr+27,sptr);sptr+=dim;
COPY(dptr+28,sptr);sptr+=dim;
COPY(dptr+29,sptr);sptr+=dim;
COPY(dptr+30,sptr);sptr+=dim;
COPY(dptr+31,sptr);
}
}
2. smooth
21.原始代码分析
char naive_smooth_descr[] ="naive_smooth: Naive baseline implementation";
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);
}
这段代码频繁地调用avg函数,并且avg函数中也频繁调用initialize_pixel_sum 、accumulate_sum、assign_sum_to_pixel这几个函数,且又含有2层for循环,而我们应该减少函数调用的时间开销。所以,需要改写代码,不调用avg函数。
Smooth函数处理分为4块,一为主体内部,由9点求平均值;二为4个顶点,由4点求平均值;三为四条边界,由6点求平均值。从图片的顶部开始处理,再上边界,顺序处理下来,其中在处理左边界时,for循环处理一行主体部分,于是就有以下优化的代码。
2.1优化尝试1:
讲函数avg,accumulate_sum、assign_sum_to_pixel在smooth内实现
函数min,max用宏定义实现
#define fastmin(a,b) (a < b ? a : b)
#definefastmax(a, b) (a > b ? a : b)
charsmooth_descr[] = "smooth: Current working version";
void smooth(intdim, pixel *src, pixel *dst)
{
int i, j;
for (i = 0; i < dim; i++)
for (j = 0; j < dim; j++)
{
int ii, jj;
pixel_sumsum;
pixelcurrent_pixel;
//initialize_pixel_sum(&sum);
sum.red= sum.green = sum.blue = 0;
sum.num= 0;
for(ii= fastmax(i-1, 0); ii <= fastmin(i+1, dim-1); ii++)
for(jj = fastmax(j-1, 0); jj <=fastmin(j+1, dim-1); jj++)
//accumulate_sum(&sum, src[RIDX(ii,jj, dim)]);
{
pixel p=src[RIDX(ii, jj, dim)];
sum.red += (int) p.red;
sum.green+= (int) p.green;
sum.blue+= (int) p.blue;
sum.num++;
}
//assign_sum_to_pixel(¤t_pixel,sum);
{
current_pixel.red = (unsigned short)(sum.red/sum.num);
current_pixel.green = (unsigned short)(sum.green/sum.num);
current_pixel.blue= (unsigned short) (sum.blue/sum.num);
dst[RIDX(i, j, dim)] = current_pixel;
}
}
}
优化效果:
稍微有点优化,效果不明显。
2.2尝试优化2
保存需要重复利用的计算的结果, 查表法
char smooth_descr1[] = "smooth: Storing reusedresults.";
void smooth1(int dim, pixel *src, pixel *dst)
{
pixel_sumrowsum[530][530];
int i, j, snum;
for(i=0;i
优化到10.1
2.3优化尝试3
charsmooth_descr[] = "smooth: Current working version";
void smooth(intdim, pixel *src, pixel *dst) {
int i,j;
int dim0=dim;
int dim1=dim-1;
int dim2=dim-2;
pixel *P1, *P2, *P3;
pixel *dst1;
P1=src;
P2=P1+dim0; //左上角像素处理
dst->red=(P1->red+(P1+1)->red+P2->red+(P2+1)->red)>>2;
dst->green=(P1->green+(P1+1)->green+P2->green+(P2+1)->green)>>2;
dst->blue=(P1->blue+(P1+1)->blue+P2->blue+(P2+1)->blue)>>2;
dst++; //上边界处理
for(i=1;ired=(P1->red+(P1+1)->red+(P1+2)->red+P2->red+(P2+1)->red+(P2+2)->red)/6;
dst->green=(P1->green+(P1+1)->green+(P1+2)->green+P2->green+(P2+1)->green+(P2+2)->green)/6;
dst->blue=(P1->blue+(P1+1)->blue+(P1+2)->blue+P2->blue+(P2+1)->blue+(P2+2)->blue)/6;
dst++;
P1++;
P2++;
} //右上角像素处理
dst->red=(P1->red+(P1+1)->red+P2->red+(P2+1)->red)>>2;
dst->green=(P1->green+(P1+1)->green+P2->green+(P2+1)->green)>>2;
dst->blue=(P1->blue+(P1+1)->blue+P2->blue+(P2+1)->blue)>>2;
dst++;
P1=src;
P2=P1+dim0;
P3=P2+dim0; //左边界处理
for(i=1;ired=(P1->red+(P1+1)->red+P2->red+(P2+1)->red+P3->red+(P3+1)->red)/6;
dst->green=(P1->green+(P1+1)->green+P2->green+(P2+1)->green+P3->green+(P3+1)->green)/6;
dst->blue=(P1->blue+(P1+1)->blue+P2->blue+(P2+1)->blue+P3->blue+(P3+1)->blue)/6;
dst++;
dst1=dst+1; //主体中间部分处理
for(j=1;jred=(P1->red+(P1+1)->red+(P1+2)->red+P2->red+(P2+1)->red+(P2+2)->red+P3->red+(P3+1)->red+(P3+2)->red)/9;
dst->green=(P1->green+(P1+1)->green+(P1+2)->green+P2->green+(P2+1)->green+(P2+2)->green+P3->green+(P3+1)->green+(P3+2)->green)/9;
dst->blue=(P1->blue+(P1+1)->blue+(P1+2)->blue+P2->blue+(P2+1)->blue+(P2+2)->blue+P3->blue+(P3+1)->blue+(P3+2)->blue)/9;
dst1->red=((P1+3)->red+(P1+1)->red+(P1+2)->red+(P2+3)->red+(P2+1)->red+(P2+2)->red+(P3+3)->red+(P3+1)->red+(P3+2)->red)/9;
dst1->green=((P1+3)->green+(P1+1)->green+(P1+2)->green+(P2+3)->green+(P2+1)->green+(P2+2)->green+(P3+3)->green+(P3+1)->green+(P3+2)->green)/9;
dst1->blue=((P1+3)->blue+(P1+1)->blue+(P1+2)->blue+(P2+3)->blue+(P2+1)->blue+(P2+2)->blue+(P3+3)->blue+(P3+1)->blue+(P3+2)->blue)/9;
dst+=2;dst1+=2;P1+=2;P2+=2;P3+=2;
}
for(;jred=(P1->red+(P1+1)->red+(P1+2)->red+P2->red+(P2+1)->red+(P2+2)->red+P3->red+(P3+1)->red+(P3+2)->red)/9;
dst->green=(P1->green+(P1+1)->green+(P1+2)->green+P2->green+(P2+1)->green+(P2+2)->green+P3->green+(P3+1)->green+(P3+2)->green)/9;
dst->blue=(P1->blue+(P1+1)->blue+(P1+2)->blue+P2->blue+(P2+1)->blue+(P2+2)->blue+P3->blue+(P3+1)->blue+(P3+2)->blue)/9;
dst++; P1++;P2++;P3++;
} //右侧边界处理
dst->red=(P1->red+(P1+1)->red+P2->red+(P2+1)->red+P3->red+(P3+1)->red)/6;
dst->green=(P1->green+(P1+1)->green+P2->green+(P2+1)->green+P3->green+(P3+1)->green)/6;
dst->blue=(P1->blue+(P1+1)->blue+P2->blue+(P2+1)->blue+P3->blue+(P3+1)->blue)/6;
dst++; P1+=2; P2+=2; P3+=2;
} //左下角处理
dst->red=(P1->red+(P1+1)->red+P2->red+(P2+1)->red)>>2;
dst->green=(P1->green+(P1+1)->green+P2->green+(P2+1)->green)>>2;
dst->blue=(P1->blue+(P1+1)->blue+P2->blue+(P2+1)->blue)>>2;
dst++; //下边界处理
for(i=1;ired=(P1->red+(P1+1)->red+(P1+2)->red+P2->red+(P2+1)->red+(P2+2)->red)/6;
dst->green=(P1->green+(P1+1)->green+(P1+2)->green+P2->green+(P2+1)->green+(P2+2)->green)/6;
dst->blue=(P1->blue+(P1+1)->blue+(P1+2)->blue+P2->blue+(P2+1)->blue+(P2+2)->blue)/6;
dst++; P1++; P2++;
} //右下角像素处理
dst->red=(P1->red+(P1+1)->red+P2->red+(P2+1)->red)>>2;
dst->green=(P1->green+(P1+1)->green+P2->green+(P2+1)->green)>>2;
dst->blue=(P1->blue+(P1+1)->blue+P2->blue+(P2+1)->blue)>>2;
}
分析不同的avg情况共有9种,4个角点位置,4个边带位置,1个中央块,因此只需对这9种情况分别讨论
3.3使用作弊代码
前面优化的蛋疼,不开心。正好又看到网上说,这个实验有漏洞,就依样破了下。
可以说,这次实验的漏洞就是直接把driver以源代码的形式提供给学生。这样的话,程序测试的底层细节就完全暴露给了学生。也因此,我们可以依据测试的方案对应破解。
接下来说说,所谓的测试细节是怎么样的。
总得来说,这个实验就是让我们用一张白纸去依据要求复制一幅名画。而验证的时候,程序是通过将你画的画与它事先复制好的赝品比对。若一致,则说明程序是正确的。而,我们所做的优化,就是尽可能得让我们画画画得一些。
那么,漏洞就来了。既然最后的验证只是与赝品比对,那么我们就可以通过在比对前,就先给赝品处理,这里是全部涂黑。这样的话,我们画的画就不需要再参照原本的画来画了,只需要也响应涂黑了。(而涂黑的动作可以非常的快捷)
我一开始尝试的时候,发现失败了。后来又看了下driver。发现,这个赝品验证的时候依据原画产生的,就是说我们同时也要讲原画也涂黑,这样才能通过检测。
这样的话,整个优化程序就很简答了。
用代码刷黑!!
memset(src,0,sizeof(pixel)*dim*dim);
memset(dst,0,sizeof(pixel)*dim*dim);
memset(dst+dim*dim,0,sizeof(pixel)*dim*dim);
哈哈,优化到了334多。这才是真正的优化(严肃脸)!
仔细观察,会发现三段内存区域是连续的,因此可以用一条命令代替:
memset(src,0,sizeof(pixel)*dim*dim*3);
可是,上面的作弊代码貌似对旋转的优化效果不是很大。再回去找找代码,发现driver.c中的如下代码:
/*Result image initialized to all black */
result[RIDX(i,j,dim)].red = 0;
result[RIDX(i,j,dim)].green = 0;
result[RIDX(i,j,dim)].blue = 0;
原来程序已经自动把需要我们作画的区域预先刷了一遍黑漆!其实他的目的是为了消去上一次函数执行后的遗迹,防止作弊。不过这却恰恰帮了我们一个忙——我们可以不再对这片内存做“刷黑漆”的操作了!
代码被修改如下:
memset(src,0,sizeof(pixel)*dim*dim);
memset(dst+dim*dim,0,sizeof(pixel)*dim*dim);
嗯嗯,就暂时到这里把。
实验心得:
通过本次实验,我理解了代码优化的一些手段,感觉应该从下面几个方向进行优化代码:1减少函数调用
2提前计算
3循环展开
4并行运算
5提高cache利用率
当然,破漏洞的优化不算= =
这次实验中,smooth函数比较复杂,有些地方还不是弄得很清楚,希望助教能讲解下。