2009-01-21 92 views
40

有沒有像C#中++ i和i ++之間有任何性能差異嗎?

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

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

使用的東西之間的性能差異或者是編譯器能夠以這樣的方式,他們的情況一樣快,他們在功能上等同於優化?

編輯: 這個問題是因爲我和同事討論過這個問題,並不是因爲我認爲它在任何實際意義上都是有用的優化。它主要是學術性的。

+1

這應該沒有關閉。但無論如何,如果你編譯它並使用ildasm.exe來查看MSIL,你會看到這兩個例子導致相同的MSIL。 – 2009-01-21 22:50:21

+0

投票重新開放。致John Sheehan,tvanfosson,ctacke,Marc Gravell ..如果您投票結束,請編輯並提供問題中的副本!我看到沒有鏈接到一個騙局 – mmcdole 2009-01-21 22:54:10

+0

有一個與重複的答案,我不知道它去了哪裏。 – 2009-01-21 23:28:31

回答

34

在這種情況下,生成的++ i和i ++中間代碼沒有區別。鑑於此程序:

class Program 
{ 
    const int counter = 1024 * 1024; 
    static void Main(string[] args) 
    { 
     for (int i = 0; i < counter; ++i) 
     { 
      Console.WriteLine(i); 
     } 

     for (int i = 0; i < counter; i++) 
     { 
      Console.WriteLine(i); 
     } 
    } 
} 

生成的IL代碼是相同的兩個循環:

IL_0000: ldc.i4.0 
    IL_0001: stloc.0 
    // Start of first loop 
    IL_0002: ldc.i4.0 
    IL_0003: stloc.0 
    IL_0004: br.s  IL_0010 
    IL_0006: ldloc.0 
    IL_0007: call  void [mscorlib]System.Console::WriteLine(int32) 
    IL_000c: ldloc.0 
    IL_000d: ldc.i4.1 
    IL_000e: add 
    IL_000f: stloc.0 
    IL_0010: ldloc.0 
    IL_0011: ldc.i4  0x100000 
    IL_0016: blt.s  IL_0006 
    // Start of second loop 
    IL_0018: ldc.i4.0 
    IL_0019: stloc.0 
    IL_001a: br.s  IL_0026 
    IL_001c: ldloc.0 
    IL_001d: call  void [mscorlib]System.Console::WriteLine(int32) 
    IL_0022: ldloc.0 
    IL_0023: ldc.i4.1 
    IL_0024: add 
    IL_0025: stloc.0 
    IL_0026: ldloc.0 
    IL_0027: ldc.i4  0x100000 
    IL_002c: blt.s  IL_001c 
    IL_002e: ret 

這就是說,有可能(儘管可能性很小)JIT編譯器可以做在某些情況下一些優化這將有利於一個版本的優勢。但是,如果有這樣的優化,它可能只會影響循環的最終(或者可能是第一次)迭代。

簡而言之,在所描述的循環構造中,控制變量的簡單預增量或後增量的運行時間沒有區別。

0

根據this answer,i ++使用超過++ i的一個CPU指令。但是,這是否會導致性能差異,我不知道。

由於任何一個循環都可以輕鬆地重寫爲使用後增量或預增量,所以我猜編譯器總是會使用更高效的版本。

4

夥計們,「答案」是針對C和C++的。

C#是一種不同的動物。

使用ILDASM查看編譯輸出以驗證是否存在MSIL差異。

7

啊...再次打開。好。這筆交易。

ILDASM是一個開始,但不是結束。關鍵是:JIT爲彙編代碼生成什麼?

這就是你想要做的。

拿一些你想要看的樣品。顯然,如果你願意,你可以掛鐘 - 但我想你想知道更多。

以下是不明顯的。 C#編譯器會在很多情況下生成一些非最優的MSIL序列。它調整了JIT來處理來自其他語言的這些和怪癖。問題:只有人注意到的'怪癖'已被調整。

你真的想製作一個樣本,讓你的實現嘗試,返回到主要(或任何地方),睡眠(),或者你可以附加調試器的東西,然後再次運行例程。

你不想在調試器下啓動代碼,否則JIT將生成非優化代碼 - 聽起來你想知道它在真實環境中的表現。 JIT這樣做是爲了最大限度地提高調試信息的質量,並將當前源位置從「跳來跳去」最小化。切勿在調試器下啓動perf評估。

好的。所以一旦代碼運行一次(即:JIT已經爲它生成代碼),那麼在睡眠期間(或其他)連接調試器。然後看看爲這兩個例程生成的x86/x64。

我的直覺告訴我,如果你正在使用++ i/i ++,就像你描述的那樣 - 即:在獨立表達式中右值結果不被重用 - 不會有區別。但找到並看到所有整齊的東西不是很有趣! :)

3

有一個具體的代碼和CLR釋放記?如果是這樣,請以此爲基準如果沒有,忘記它。微型優化,以及所有這些...此外,你甚至不能確定不同的CLR版本會產生相同的結果。

0
static void Main(string[] args) { 
    var sw = new Stopwatch(); sw.Start(); 
    for (int i = 0; i < 2000000000; ++i) { } 
    //int i = 0; 
    //while (i < 2000000000){++i;} 
    Console.WriteLine(sw.ElapsedMilliseconds); 

3個運行平均:
與我++:1307 與++我:1314

而與我++:1261 同時用++我:1276

這是一個賽揚D在2,53 Ghz。每次迭代需要大約1.6個CPU週期。這意味着CPU每個週期執行多於1條指令,或者JIT編譯器展開循環。 i ++和++ i之間的差異每次迭代只有0.01個CPU週期,可能是由後臺的OS服務引起的。

2

如果你問這個問題,你試圖解決錯誤的問題。

要問的第一個問題是「如何通過使其運行速度更快來提高客戶對我的軟件的滿意度?」答案几乎從來沒有「使用++我而不是i ++」,反之亦然。

從編碼恐怖的文章 「Hardware is Cheap, Programmers are Expensive」:

規則優化:
規則1:不要做。
規則2(僅適用於專家):不要這樣做。
- M.A. Jackson

我讀第2條是指「滿足客戶的需求,第一次寫乾淨,清晰的代碼,然後加速它的地方太慢了」。 ++ii++即將成爲解決方案的可能性極小。

3

作爲Jim Mischel has shown,編譯器將爲編寫for循環的兩種方式生成相同的MSIL。

但是那就是:沒有理由推測JIT或執行速度測量。如果兩行代碼生成相同的MSIL,則不僅它們的性能相同,而且它們實際上是相同的。

沒有可能的JIT將能夠區分循環,所以生成的機器代碼也必須是相同的。

2

除了其他答案,如果您的i不是int,可能會有差異在C++中,如果它是運算子++()++(int)重載的運算符類的對象,則它可能會產生差異,並可能產生副作用。性能++i應該在這種情況下更好(取決於實施)。

相關問題