2011-06-18 28 views
15

我在.NET中發現了這個奇怪的行爲,甚至在再次調查CLR via C#後,我仍然感到困惑。假設我們有一個接口與一個方法和imlements一類是:C#:爲什麼調用實現的接口方法對於類變量比接口變量更快?

interface IFoo 
{ 
    void Do(); 
} 

class TheFoo : IFoo 
{ 
    public void Do() 
    { 
     //do nothing 
    } 
} 

然後我們只想實例化這個類,並把這個DO()方法有很多次在兩個方面:使用混凝土類變量和使用接口變量:

TheFoo foo1 = new TheFoo(); 

Stopwatch stopwatch = new Stopwatch(); 
stopwatch.Start(); 
for (long i = 0; i < 1000000000; i++) 
    foo1.Do(); 
stopwatch.Stop(); 
Console.Out.WriteLine("Elapsed time: " + stopwatch.ElapsedMilliseconds); 

IFoo foo2 = foo1; 

stopwatch = new Stopwatch(); 
stopwatch.Start(); 
for (long i = 0; i < 1000000000; i++) 
    foo2.Do(); 
stopwatch.Stop(); 
Console.Out.WriteLine("Elapsed time: " + stopwatch.ElapsedMilliseconds); 

令人驚訝的(至少對我來說)所經過的時間是大約10%的不同:

Elapsed time: 6005 
Elapsed time: 6667 

所不同的是不那麼多,所以我會不在大多數情況下,不用擔心這一點。然而,我只是無法弄清楚爲什麼會發生這種情況,即使在查看IL代碼後,如果有人向我指出我明顯缺少的東西,我將不勝感激。

+0

我跑你的測試,我其實有一個非常不同的結果。已用時間:12125年 已用時間:11682。我明顯在較慢的機器上運行。衡量這樣的表現非常困難,因爲有些因素可能並不明確。 –

+1

@克雷格可能你有一些過程受到干擾。我在這個微型基準測試中有10次非常一致的結果。 –

+0

@Ivan我發佈了多次,並獲得一致的結果。我也剛剛去,並重新把它放到一個循環中,並將它連續運行10次。我在機器上運行的很少,我一直看到第一種情況變慢。它現在差不多是2:1。我還添加了另一個明確實現接口並對其進行測試的類。它與第二個案例大致相同。你使用的是什麼版本的框架?也許這會導致差異。 –

回答

17

您必須查看機器代碼才能看到發生了什麼。當你這樣做時,你會發現抖動優化器完全刪除了對foo1.Do()的調用。類似這樣的小方法被優化器內聯。由於該方法的主體不包含代碼,因此根本不會生成機器代碼。它不能對接口調用進行相同的優化,但它不足以反向設計接口方法指針實際指向空方法。

查看this answer瞭解由抖動執行的常見優化列表。請注意該答案中提到的關於分析的警告。

注意:在發佈版本中查看機器代碼需要更改選項。默認情況下,優化器在調試代碼時被禁用,即使在發佈版本中也是如此。工具+選項,調試,常規,取消「在模塊加載時抑制JIT優化」。

+0

事情是我在調試器中看到了生成的彙編代碼。它沒有被抖動優化。但無論如何這是一個好點。 +1 –

+0

爲了排除優化影響,我剛剛包含每個Do()調用都增加的靜態int變量。最後,'x'進入控制檯,這樣編譯器也不能消除它。時間稍微變長但趨勢仍然相同。 –

+0

@Ivan:*您是如何看到產生的代碼沒有被抖動優化的?人們經常犯這個錯誤。你必須做的是確保代碼在你連接調試器之前就被激化了。抖動知道調試器是否連接,並且對優化的要求較低,如果它知道調試器已連接,就很難設置斷點。 –

1

那麼,編譯器不能在一般情況下,當調用接口方法時應該執行哪個實際的方法體,因爲不同的類可能有不同的實現。

因此,當CLR面臨接口調用時,它會在封裝類型的接口映射中看到它,並檢查它應該調用哪個具體方法。實際上它低於IL。

UPD:IMO它不是callcallvirt之間的差異。

CLR在類類型上遇到callvirt時應該做些什麼?獲取被調用者的類型,查看其虛擬方法表,找到正在調用的方法並調用它。

接口類型遇到callvirt時該怎麼辦?那麼,除了prev點之外,它還應該檢查諸如顯式接口實現之類的東西。因爲你可能有兩個具有相同簽名的方法 - 一個是類的方法,另一個是顯式的接口實現。在處理類類型時,這種事情不存在。我認爲這是主要的區別。

UPD2:現在我確定是這樣。有關實際實施細節,請參閱this

相關問題