2010-07-10 54 views
1

我做一個函數的兩個版本的基準來篩選鏈表,一個是接收謂詞函數作爲參數,並在其中的謂語使用「宏模板」構建到函數中。我希望第一個運行速度更慢,因爲它在每次迭代中都會進行函數調用,但在我的盒子上它們的速度大致相同。關於爲什麼會發生這種情況的任何線索?任何幫助表示讚賞。試驗條件相同

下面是代碼:

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/time.h> 

struct list_t { 
    int val; 
    struct list_t *next; 
}; 
typedef struct list_t list_t; 

// Removes elements not matching the predicate. 
// NOTE: This is not freeing the removed elements. 

list_t *keep(int (*predfun)(int,int), int predarg, list_t *list) { 
    list_t *this, *prev; 

    for (this=list, prev=NULL; this; prev=this, this=this->next) { 
     if (!predfun(this->val, predarg)) { 
      if (!prev) { 
       list = this->next; 
      } 
      else { 
       prev->next = this->next; 
      } 
     } 
    } 

    return list; 
} 

int less(int a, int b) { 
    return a<b; 
} 

// A "template" macro for the keep function. 
// The idea is to embed the predicate into the function, so that we 
// don't have to make a function call on each iteration. 

#define build_keep(test) {           \ 
    list_t *this, *prev;            \ 
                    \ 
    for (this=list, prev=NULL; this; prev=this, this=this->next) { \ 
     if (!(test)) {            \ 
      if (!prev) {            \ 
       list = this->next;         \ 
      }              \ 
      else {             \ 
       prev->next = this->next;        \ 
      }              \ 
     }               \ 
    }                \ 
    return list;              \ 
} 


list_t *keep_less(int arg, list_t *list) { 
    build_keep(this->val < arg); 
} 

#define LEN 1000000 
#define MOD 1024 

// Creates a new list. 
list_t *buildlist() { 
    int i; 
    list_t *list, *last, *t; 
    list=NULL, last=NULL; 
    srand(0); // Using always the same seed for the benchmark. 
    for (i=0; i<LEN; i++) { 
     if (!last) { 
      last = malloc(sizeof(list_t)); 
      list = last; 
     } 
     else { 
      last->next = malloc(sizeof(list_t)); 
      last = last->next; 
     } 
     last->val = rand() % MOD; 
    } 
    last->next = NULL; 
    return list; 
}  

int main() { 
    struct timeval t0, t1; 
    list_t *list, *t; 

    // With macro. 
    list = buildlist(); 
    //for (t=list; t; t=t->next) printf("%d ", t->val); printf("\n"); 
    gettimeofday(&t0, NULL); 
    keep_less(500, list); 
    gettimeofday(&t1, NULL); 
    printf("keep_less: %lf\n", (1000000 * (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec))/1000000.0); 
    //for (t=list; t; t=t->next) printf("%d ", t->val); printf("\n"); 

    printf("\n"); 

    // Without macro. 
    list = buildlist(); 
    //for (t=list; t; t=t->next) printf("%d ", t->val); printf("\n"); 
    gettimeofday(&t0, NULL); 
    keep(less, 500, list); 
    gettimeofday(&t1, NULL); 
    printf("keep: %lf\n", (1000000 * (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec))/1000000.0); 
    //for (t=list; t; t=t->next) printf("%d ", t->val); printf("\n"); 

    return 0; 
} 

輸出這裏:

keep_less: 0.181019 

keep: 0.185590 

回答

0

我不會說的結果是差不多的 - 你看到一個4 milisecond(〜2% )有利於立即版本的差異。

這實際上是比較可觀 - 這是可能實現這樣的高儲蓄與儲蓄僅僅只因爲你的測試功能確實很少開始一個函數調用。如果你期望更多的優化,你可能會偶然發現一個important lesson ...(那個人,我必須每幾周重新學習一次:)]

+0

沒錯。我以1000000次函數調用爲基準測試了100萬次,而差異大約是相同的(6ms)。所以,我想我已經學會了(至少現在)。謝謝! – 2010-07-10 02:17:25

0

由於Ofek沒有真正回答這個問題, - )這是爲什麼會發生這種情況?。很明顯,編譯器在這裏做得非常好。

  • 函數調用本身並不 那樣難。很多人認爲,即使 如果通過函數指針去。 在 循環,其中的功能則是在 第一 迭代後的指令完成緩存時,這是特別真實。
  • 如果所有功能代碼是存在於一個 編譯單元的任何良好 編譯器現在將能夠 inline小函數(如 less)到調用方。
  • 在極端情況下,通過不斷 傳播,它甚至可能沒有動態調用遠 可言。 所有less是恆定的,在這裏,在keep被調用的地方。

沒有過度優化對代碼沒有類似的影響,而是不存在於同一個單元中,我傾向於相當系統地使用inline。這並不能保證函數將被內聯,而只是允許在頭文件中定義函數體。