2011-05-30 81 views
8

我有這樣的代碼(我的strlen函數)* STR和*海峽++

size_t slen(const char *str) 
{ 
    size_t len = 0; 
    while (*str) 
    { 
     len++; 
     str++; 
    } 
    return len; 
} 

while (*str++),如下圖所示,程序執行時間要大得多:

while (*str++) 
{ 
    len++; 
} 

我做這探測代碼

int main() 
{ 
    double i = 11002110; 
    const char str[] = "long string here blablablablablablablabla" 
    while (i--) 
     slen(str); 

    return 0; 
} 

在第一種情況下,執行時間大約爲6.7秒,而在第二種情況下(使用*str++),時間約10秒!

爲什麼如此大的差別?

+5

爲什麼使用double而不是unsigned long?另外,您應該嘗試編譯而不進行優化並查看結果。哦,你應該運行約二十次,並計算平均持續時間。 – 2011-05-30 20:01:09

+0

分支預測失敗?不必要的數據副本?嘗試查看生成的程序集。此外,嘗試開啓優化,它可能會解決問題。 – dmckee 2011-05-30 20:02:25

+2

你使用什麼樣的編譯器?我用gcc 4.4.5運行它,它們幾乎在同一時間,大約2s。隨着我設置爲110021100,他們都使用大約19秒。 – 2011-05-30 20:03:10

回答

6

可能是因爲後增量操作符(在while語句的條件中使用)涉及使用其舊值保留該變量的臨時副本。

什麼while (*str++)真正的意思是:

while (tmp = *str, ++str, tmp) 
    ... 

與此相反,當你寫str++;作爲while循環的身體一個語句,它是在一個無效的情況下,因此,舊的值不因爲不需要而被抓取。

總之,在你有一個分配,增量爲2,並在每個循環迭代一個跳*str++情況。在另一種情況下,你只有2個增量和一個跳躍。

+0

但是在兩種情況下都有一個'test(* str)'和'inc(str)' - 從高級角度來看,所做的工作是一樣的。 – 2011-05-30 20:06:51

+4

它與正派的編譯器無關。 – delnan 2011-05-30 20:12:02

+2

@pst:原則上,增量後總是包含一個副本。在實踐中,複製通常可能會被忽略,但取決於編譯器,聲明的確切上下文以及優化設置,它可能實際上可能會或可能不會實現。 – dmckee 2011-05-30 20:12:26

2

上ideone.com嘗試了這一點,我與*海峽++ here約0.5秒執行。沒有,它需要超過一秒鐘(here)。使用* str ++更快。也許在* str ++上優化可以更高效地完成。

1

這取決於你的編譯器,編譯器標誌,和你的架構。隨着蘋果的LLVM GCC 4.2.1,我沒有得到在兩個版本之間的性能顯着變化,而且確實不應該。一個好的編譯器會打開*str版本弄成

IA-32(AT & T語法):

slen: 
     pushl %ebp    # Save old frame pointer 
     movl %esp, %ebp  # Initialize new frame pointer 
     movl -4(%ebp), %ecx # Load str into %ecx 
     xor %eax, %eax  # Zero out %eax to hold len 
loop: 
     cmpb (%ecx), $0  # Compare *str to 0 
     je done    # If *str is NUL, finish 
     incl %eax    # len++ 
     incl %ecx    # str++ 
     j  loop    # Goto next iteration 
done: 
     popl %ebp    # Restore old frame pointer 
     ret     # Return 

*str++版本可以被編譯完全相同(因爲更改str是不可見外slen,當增量實際發生並不重要),或在循環體可能是:

loop: 
     incl %ecx    # str++ 
     cmpb -1(%ecx), $0  # Compare *str to 0 
     je done    # If *str is NUL, finish 
     incl %eax    # len++ 
     j  loop    # Goto next iteration 
1

其他人已經提供了一些優秀的commen包括對生成的彙編代碼的分析。我強烈建議你仔細閱讀。正如他們指出的,這種問題如果沒有量化就不能真正回答,所以讓我們一起玩吧。

首先,我們將需要一個程序。我們的計劃是這樣的:我們將生成長度爲2的字符串,並依次嘗試所有函數。我們運行一次來​​初始化緩存,然後分別使用我們可用的最高分辨率進行4096次迭代。一旦完成,我們將計算一些基本的統計數據:min,max和簡單移動平均值並轉儲它。然後我們可以做一些基本的分析。

除了你已經顯示的兩種算法之外,我將展示第三個選項,它根本不涉及使用計數器,而是依賴於減法,我將通過投擲來混合東西在std::strlen,只是爲了看看會發生什麼。這將是一個有趣的回合。

通過我們的小程序已經寫入電視的魔力,所以我們用gcc -std=c++11 -O3 speed.c編譯它,我們得到起動產生一些數據。我做了兩個單獨的圖,一個是字符串,大小從32到8192字節,另一個是字符串,長度從16384到1048576字節。在下面的圖表中,Y軸是在納秒內消耗的時間,X軸以字節爲單位顯示字符串的長度。

事不宜遲,讓我們來看看從32 「小」 的字符串表現爲8192個字節:

Performance Plot - Small Strings

現在是有趣的。 std::strlen的功能不僅僅是性能優於所有的功能,而且它的性能也更加穩定。

會,形勢的變化,從16384如果我們看一下大串一路1048576個字節長?

enter image description here

的排序。差異變得更加明顯。由於我們的自定義編寫的功能唾手可得,std::strlen繼續執行令人欽佩。

一個有趣的現象,使的是,你不一定能轉化的C++的指令數(甚至,彙編指令數)的性能,因爲其功能包括機構較少的指令有時需要更長的時間來執行。

更有意思的是 - 和重要觀察是要注意str::strlen功能如何執行。

那麼,這一切是什麼讓我們?

第一個結論:不要重新發明輪子。使用可用的標準功能。它們不僅是已經寫好的,而且它們的優化程度非常高,幾乎肯定會勝過你可以編寫的任何東西,除非你是Agner Fog。第二個結論:除非你有一個硬數據從一個代碼或函數的特定部分是你的應用程序中的熱點,不要打擾優化代碼。程序員通過查看高級功能在檢測熱點方面非常不好。

第三個結論:寧可爲了提高代碼的性能算法最優化。讓你的思想工作,並讓編譯器隨機播放。

您原來的問題是:「爲什麼函數slen2慢於slen1?」我可以說,沒有更多的信息就不容易回答,即使如此,它可能會比你所關心的要長得多,涉及更多。相反,我會說這是:

誰在乎,爲什麼?你爲什麼還要打擾呢?使用std::strlen - 這比任何你可以設置的更好 - 然後繼續解決更重要的問題 - 因爲我確信這個不是是你應用程序中最大的問題。

相關問題