【CSAPP】Performance Lab 實驗筆記


perflab這節的任務是利用書中知識,來對圖像處理中的Rotate和Smooth操作函數進行優化。這次沒對上電波,覺得學了一堆屠龍之技。於我個人理解,現在計算機配置比以前高多了,連SWAP分區都幾近廢棄了,對於一般開發者來講,代碼效率瓶頸首先是架構,其次是算法,最后才是書里教的這些小細節。而且這節也沒個具體的分數標准,優化了半天也不知道自己寫的算啥水平,缺了前面幾節那種攻克難題的成就感。不過也有可能是因為我太菜了 XD

前期准備

這次的開發環境被我遷移到了WSL上,系統版本為ubuntu 18.04 LTS, 使用VSCode remote作為主要編輯器,軟件包只裝了以下幾個:

sudo apt-get install build-essential #安裝gcc、make等常用開發工具
sudo apt-get install libc6-dev		#安裝c++庫
sudo apt-get install g++-multilib	#讓64位機器可以編譯32位程序

知識點

主要是CSAPP第五章和第六章所總結的一些小技巧

  • 消除冗余的函數調用。比如避免在for循環里用strlen。
  • 消除不必要的內存引用。比如引入臨時變量來把中間結果保存到寄存器里,在全部計算完成后把最終結果存到數組或全局變量里。
  • 循環展開,降低判斷語句和增減循環變量的開銷。
  • 累積變量和重新組合,提高指令並行性。
  • 功能性風格重寫條件操作,即用三元運算符。
  • 提高空間局部性,盡量按照數組在內存里存儲的順序,以1為步長進行讀取。
  • 提高時間局部性,一旦在內存里讀出一個變量,就盡可能頻繁且集中的使用它。

Rotate

對於Rotate操作,我主要優化了以下幾點:

  1. 因為高速緩存讀操作不命中的懲罰比寫操作高,又因為空間局部性原則,所以優先在dst數組上以1為步長遍歷。

  2. 為了消除冗余的運算,我們可以對RIDX宏進行拆解,分析可知

dst[dim*dim-dim + i - dim*j] == src[dim*i + j]
  1. 根據講義里提示所有圖片尺寸為32的倍數,又因為CACHE_BLOCK大小為32,所以我們可對原代碼進行32路展開
  2. 同樣是根據空間局部性原則,盡量使內部循環步長短於外部循環
void rotate(int dim, pixel *src, pixel *dst)
{
    // dst = dim*dim-dim + i - dim*j
    // src = dim*i + j
    int i,j;
    dst+=(dim*dim-dim);
    for(i=0;i<dim;i+=32){
        for(j=0;j<dim;j++){
            dst[0]=src[0];
            dst[1]=src[dim];
            dst[2]=src[2*dim];
            dst[3]=src[3*dim];
            dst[4]=src[4*dim];
            dst[5]=src[5*dim];
            dst[6]=src[6*dim];
            dst[7]=src[7*dim];
            dst[8]=src[8*dim];
            dst[9]=src[9*dim];
            dst[10]=src[10*dim];
            dst[11]=src[11*dim];
            dst[12]=src[12*dim];
            dst[13]=src[13*dim];
            dst[14]=src[14*dim];
            dst[15]=src[15*dim];
            dst[16]=src[16*dim];
            dst[17]=src[17*dim];
            dst[18]=src[18*dim];
            dst[19]=src[19*dim];
            dst[20]=src[20*dim];
            dst[21]=src[21*dim];
            dst[22]=src[22*dim];
            dst[23]=src[23*dim];
            dst[24]=src[24*dim];
            dst[25]=src[25*dim];
            dst[26]=src[26*dim];
            dst[27]=src[27*dim];
            dst[28]=src[28*dim];
            dst[29]=src[29*dim];
            dst[30]=src[30*dim];
            dst[31]=src[31*dim];
            src++;      //j++ => src+=1
            dst-=dim;   //j++ => dim+=-dim
        }    
        //i+=32 => src+=32*dim, then neutralize the effects of for(j)
        src+=31*dim;
        //i+=32 => dst+=32, then neutralize the effects of for(j)
        dst+=dim*dim+32;
    }
}

除此之外也嘗試過用臨時變量代替dim*dim+32,不過收效甚微。以上代碼的成績在16左右

Rotate: Version = naive_rotate: Naive baseline implementation:
Dim             64      128     256     512     1024    Mean
Your CPEs       2.8     4.2     5.3     10.6    11.5
Baseline CPEs   14.7    40.1    46.4    65.9    94.5
Speedup         5.2     9.4     8.8     6.2     8.2     7.4

Rotate: Version = rotate: Current working version:
Dim             64      128     256     512     1024    Mean
Your CPEs       2.7     2.2     2.2     2.7     4.2
Baseline CPEs   14.7    40.1    46.4    65.9    94.5
Speedup         5.4     18.0    21.0    24.8    22.6    16.3

Smooth

對於Smooth操作,我的想法很直白:

  1. avg中有大量的冗余的max和min函數調用,可通過分類討論四角、四邊、中間的邊界條件來優化之。
  2. src的每個單元格都被多次讀取,利用效率不高,可以通過復用讀取的值來減少讀取次數。

在以上思想的指導下,我又加了幾個輔助函數,最終代碼如下:

pixel_sum p_sum[512][512];
static void three_pixel_sum(pixel_sum *sum, pixel a, pixel b, pixel c)
{
    sum->red=(int)(a.red+b.red+c.red);
    sum->green=(int)(a.green+b.green+c.green);
    sum->blue=(int)(a.blue+b.blue+c.blue);
}
static void two_pixel_sum(pixel_sum *sum, pixel a, pixel b){
    sum->red=(int)(a.red+b.red);
    sum->blue=(int)(a.blue+b.blue);
    sum->green=(int)(a.green+b.green);
}
static void add_pixel_sum(pixel_sum *a, pixel_sum b){
    a->red+=b.red;
    a->green+=b.green;
    a->blue+=b.blue;
}
static void sum2pixel(pixel *current_pixel, pixel_sum sum, int num)
{
    current_pixel->red = (unsigned short)(sum.red / num);
    current_pixel->green = (unsigned short)(sum.green / num);
    current_pixel->blue = (unsigned short)(sum.blue / num);
    return;
}
void smooth(int dim, pixel *src, pixel *dst)
{
    pixel_sum sum;
    int r,c;
    int dimsubone=dim-1;
    //初始化
    for(r=0;r<dim;r++){
        for(c=0;c<dim;c++){
            initialize_pixel_sum(&p_sum[r][c]);
        }
    }
    //計算中間部分
    for(r=1;r<dimsubone;r++){
        for(c=1;c<dimsubone;c++){
            three_pixel_sum(&sum,src[RIDX(r,c-1,dim)],src[RIDX(r,c,dim)],src[RIDX(r,c+1,dim)]);
            add_pixel_sum(&p_sum[r-1][c],sum);
            add_pixel_sum(&p_sum[r][c],sum);
            add_pixel_sum(&p_sum[r+1][c],sum);
        }
    }
    //計算上下兩邊
    for(c=1;c<dimsubone;c++){
        three_pixel_sum(&sum,src[RIDX(0,c-1,dim)],src[RIDX(0,c,dim)],src[RIDX(0,c+1,dim)]);
        add_pixel_sum(&p_sum[0][c],sum);
        add_pixel_sum(&p_sum[1][c],sum);
        three_pixel_sum(&sum,src[RIDX(dimsubone,c-1,dim)],src[RIDX(dimsubone,c,dim)],src[RIDX(dimsubone,c+1,dim)]);
        add_pixel_sum(&p_sum[dim-2][c],sum);
        add_pixel_sum(&p_sum[dimsubone][c],sum);
    }
    //計算左右兩邊
    for(r=1;r<dimsubone;r++){
        two_pixel_sum(&sum,src[RIDX(r,0,dim)],src[RIDX(r,1,dim)]);
        add_pixel_sum(&p_sum[r-1][0],sum);
        add_pixel_sum(&p_sum[r][0],sum);
        add_pixel_sum(&p_sum[r+1][0],sum);
        two_pixel_sum(&sum,src[RIDX(r,dim-2,dim)],src[RIDX(r,dimsubone,dim)]);
        add_pixel_sum(&p_sum[r-1][dimsubone],sum);
        add_pixel_sum(&p_sum[r][dimsubone],sum);
        add_pixel_sum(&p_sum[r+1][dimsubone],sum);
    }
   //計算四角
  two_pixel_sum(&sum,src[RIDX(0,0,dim)],src[RIDX(0,1,dim)]);
    add_pixel_sum(&p_sum[0][0],sum);
    add_pixel_sum(&p_sum[1][0],sum);
    two_pixel_sum(&sum,src[RIDX(0,dim-2,dim)],src[RIDX(0,dimsubone,dim)]);
    add_pixel_sum(&p_sum[0][dimsubone],sum);
    add_pixel_sum(&p_sum[1][dimsubone],sum);
    two_pixel_sum(&sum,src[RIDX(dimsubone,0,dim)],src[RIDX(dimsubone,1,dim)]);
    add_pixel_sum(&p_sum[dim-2][0],sum);
    add_pixel_sum(&p_sum[dimsubone][0],sum);
    two_pixel_sum(&sum,src[RIDX(dimsubone,dim-2,dim)],src[RIDX(dimsubone,dimsubone,dim)]);
    add_pixel_sum(&p_sum[dim-2][dimsubone],sum);
    add_pixel_sum(&p_sum[dimsubone][dimsubone],sum);
    //中部有9個相鄰點
    for(r=1;r<dimsubone;r++){
        for(c=1;c<dimsubone;c++){
            sum2pixel(&dst[RIDX(r,c,dim)],p_sum[r][c],9);
        }
        sum2pixel(&dst[RIDX(r,0,dim)],p_sum[r][0],6);
        sum2pixel(&dst[RIDX(r,dimsubone,dim)],p_sum[r][dimsubone],6);
    }
    //四邊有6個相鄰點
    for(c=1;c<dimsubone;c++){
        sum2pixel(&dst[RIDX(0,c,dim)],p_sum[0][c],6);
        sum2pixel(&dst[RIDX(dimsubone,c,dim)],p_sum[dimsubone][c],6);
    }
    //四角有4個相鄰點
    sum2pixel(&dst[RIDX(0,0,dim)],p_sum[0][0],4);
    sum2pixel(&dst[RIDX(dimsubone,0,dim)],p_sum[dimsubone][0],4);
    sum2pixel(&dst[RIDX(0,dimsubone,dim)],p_sum[0][dimsubone],4);
    sum2pixel(&dst[RIDX(dimsubone,dimsubone,dim)],p_sum[dimsubone][dimsubone],4);

}

分數在23左右

Smooth: Version = naive_smooth: Naive baseline implementation:
Dim             32      64      128     256     512     Mean
Your CPEs       52.5    50.2    50.6    52.0    51.7
Baseline CPEs   695.0   698.0   702.0   717.0   722.0
Speedup         13.2    13.9    13.9    13.8    14.0    13.8

Smooth: Version = smooth: Current working version:
Dim             32      64      128     256     512     Mean
Your CPEs       28.8    29.4    29.6    30.6    32.3
Baseline CPEs   695.0   698.0   702.0   717.0   722.0
Speedup         24.1    23.7    23.8    23.4    22.3    23.5

還可以繼續利用動態規划思想進行優化,但我懶得搞了。像這種分類情況多,代碼量大的題目我確實是不怎么喜歡做。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM