無止境的內存優化——停不下的循環

小夥伴們是否是跟我同樣,覺得以前的內存優化已經完成了?不,這纔剛剛開始……讓咱們一塊兒進入這無休止的循環吧!數組

switch語句和查找表 / Switch statement vs. lookup tables

switch語句一般用於如下狀況:緩存

調用幾個函數中的一個app

設置一個變量或返回值函數

執行幾個代碼片段中的一個oop

若是case表示是密集的,在使用switch語句的前兩種狀況中,可使用效率更高的查找表。好比下面的兩個實現彙編代碼轉換成字符串的例程:性能

char * Condition_String1(int condition) {
    switch(condition) {
         case 0: return "EQ";
         case 1: return "NE";
         case 2: return "CS";
         case 3: return "CC";
         case 4: return "MI";
         case 5: return "PL";
         case 6: return "VS";
         case 7: return "VC";
         case 8: return "HI";
         case 9: return "LS";
         case 10: return "GE";
         case 11: return "LT";
         case 12: return "GT";
         case 13: return "LE";
         case 14: return "";
         default: return 0;
    }
}
char * Condition_String2(int condition) {
    if((unsigned) condition >= 15) return 0;
    return
          "EQ\0NE\0CS\0CC\0MI\0PL\0VS\0VC\0HI\0LS\0GE\0LT\0GT\0LE\0\0" +
           3 * condition;
}

第一個例程須要240個字節,第二個只須要72個。測試

循環終止 / Loop termination

若是不加留意地編寫循環終止條件,就可能會給程序帶來明顯的負擔。咱們應該儘可能使用「倒數到零」的循環,使用簡單的循環終止條件。循環終止條件相對簡單,程序在執行的時候也會消耗相對少的時間。拿下面兩個計算n!的例子來講,第一個例子使用遞增循環,第二個使用遞減循環。優化

int fact1_func (int n)
{
    int i, fact = 1;
    for (i = 1; i <= n; i++)
        fact *= i;
    return (fact);
}
int fact2_func(int n)
{
    int i, fact = 1;
    for (i = n; i != 0; i--)
        fact *= i;
    return (fact);
}

結果是,第二個例子要比第一個快得多。spa

更快的for()循環 / Faster for() loops

這是一個簡單而有效的概念,一般狀況下,咱們習慣把for循環寫成這樣:指針

for( i = 0;  i < 10;  i++){ ... }

i 值依次爲:0,1,2,3,4,5,6,7,8,9

在不在意循環計數器順序的狀況下,咱們能夠這樣:

for( i = 10;  i--; ) { ... }

i 值依次爲: 9,8,7,6,5,4,3,2,1,0,並且循環要更快

這種方法是可行的,由於它是用更快的i--做爲測試條件的,也就是說「i是否爲非零數,若是是減一,而後繼續」。相對於原先的代碼,處理器不得不「把i減去10,結果是否爲非零數,若是是,增長i,而後繼續」,在緊密循環(tight loop)中,這會產生顯著的區別。

這種語法看起來有一點陌生,卻徹底合法。循環中的第三條語句是可選的(無限循環能夠寫成這樣for(;;)),下面的寫法也能夠取得一樣的效果:

for(i = 10;  i;  i--){}

或者:

for(i = 10;  i != 0;  i--){}

咱們惟一要當心的地方是要記住循環須要中止在0(若是循環是從50-80,這樣作就不行了),並且循環的計數器爲倒計數方式。

另外,咱們還能夠把計數器分配到寄存器上,能夠產生更爲有效的代碼。這種將循環計數器初始化成循環次數,而後遞減到零的方法,一樣適用於while和do語句。

混合循環/ Loop jamming 在可使用一個循環的場合,決不要使用兩個。可是若是你要在循環中進行大量的工做,超過處理器的指令緩衝區,在這種狀況下,使用兩個分開的循環可能會更快,由於有可能這兩個循環都被完整的保存在指令緩衝區裏了。

// 原先的代碼
for(i = 0; i < 100; i++){
    stuff();
}
for(i = 0; i < 100; i++){
    morestuff();
}        
//更好的作法
for(i = 0; i < 100; i++){
    stuff();
    morestuff();
}

函數循環 / Function Looping

調用函數的時候,在性能上就會付出必定的代價。不光要改變程序指針,還要將那些正在使用的變量壓入堆棧,分配新的變量空間。爲了提升程序的效率,在程序的函數結構上,有不少工做能夠作。保證程序的可讀性的同時,還要儘可能控制程序的大小。

若是一個函數在一個循環中被頻繁調用,就能夠考慮將這個循環放在函數的裏面,這樣能夠免去重複調用函數的負擔,好比:

for(i = 0 ; i < 100 ; i++) 
{ 
    func(t,i); 
}
void func(int w, d) 
{ 
    lots of stuff. 
}

能夠寫成:

func(t);
void func(w) 
{ 
    for(i = 0; i < 100; i++) { 
        //lots of stuff. 
    } 
}

展開循環 / Loop unrolling

爲了提升效率,能夠將小的循環解開,不過這樣會增長代碼的尺寸。循環被拆開後,會下降循環計數器更新的次數,減小所執行的循環的分支數目。若是循環只重複幾回,那它徹底能夠被拆解開,這樣,由循環所帶來的額外開銷就會消失。

好比:

for(i = 0; i < 3; i++){ 
    something(i);
}
//更高效的方式:
something(0);
something(1);
something(2);

由於在每次的循環中,i 的值都會增長,而後檢查是否有效。編譯器常常會把這種簡單的循環解開,前提是這些循環的次數是固定的。對於這樣的循環:

for(i = 0; i <  limit; i++) { ... }

就不可能被拆解,由於咱們不知道它循環的次數究竟是多少。不過,將這種類型的循環拆解開並非不可能的。

與簡單循環相比,下面的代碼的長度要長不少,然而具備高得多的效率。選擇8做爲分塊大小,只是用來演示,任何合適的長度都是可行的。例子中,循環的成立條件每八次才被檢驗一次,而不是每次都要檢驗。若是須要處理的數組的大小是肯定的,咱們就可使用數組的大小做爲分塊的大小(或者是可以整除數組長度的數值)。不過,分塊的大小跟系統的緩存大小有關。

#include<stdio.H> 
#define BLOCKSIZE (8) 
int main(void)
{ 
    int i = 0; 
    int limit = 33;  /* could be anything */ 
    int blocklimit;

    /* The limit may not be divisible by BLOCKSIZE, 
      go as near as we can first, then tidy up.
     */ 
    blocklimit = (limit / BLOCKSIZE) * BLOCKSIZE;

    /* unroll the loop in blocks of 8 */ 
    while(i < blocklimit) { 
        printf("process(%d)\n", i); 
        printf("process(%d)\n", i+1); 
        printf("process(%d)\n", i+2); 
        printf("process(%d)\n", i+3); 
        printf("process(%d)\n", i+4); 
        printf("process(%d)\n", i+5); 
        printf("process(%d)\n", i+6); 
        printf("process(%d)\n", i+7); 
        /* update the counter */ 
        i += 8; 
    } 
    /* 
     * There may be some left to do.
     * This could be done as a simple for() loop, 
     * but a switch is faster (and more interesting) 
     */ 
    if( i < limit ) 
    { 
        /* Jump into the case at the place that will allow
         * us to finish off the appropriate number of items. 
         */ 
        switch( limit - i ) 
        { 
            case 7 : printf("process(%d)\n", i); i++; 
            case 6 : printf("process(%d)\n", i); i++; 
            case 5 : printf("process(%d)\n", i); i++; 
            case 4 : printf("process(%d)\n", i); i++; 
            case 3 : printf("process(%d)\n", i); i++; 
            case 2 : printf("process(%d)\n", i); i++; 
            case 1 : printf("process(%d)\n", i); 
        }
    } 
    return 0;
}

通過惰性評估和二分分解煎熬,小編覺得本身已經逃出生天了,哪知這纔剛剛開始,小夥伴們,還請持續關注更新,更多幹貨和資料請直接聯繫我,也能夠加羣710520381,邀請碼:柳貓,歡迎你們共同討論

相關文章
相關標籤/搜索