CSAPP LAB---perflab-handout性能优化

LAB4

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函数比较复杂,有些地方还不是弄得很清楚,希望助教能讲解下。

你可能感兴趣的:(csapp)