2014-09-01 21 views
5

如果你有一個方法或類型Foo<T>,那麼CLR可以爲不同的T編譯多個版本。我知道所有的引用類型共享相同的版本。它是如何工作的結構?對於不同的結構,代碼是否有時共享,或者從不共享?例如,我可以想象代碼是爲所有相同大小的結構共享的。何時在CLR中爲泛型的不同實例共享代碼?

我很感興趣,因爲我想了解一下下面的例子:

interface IBar 
{ 
    void DoBar(); 
} 

struct Baz : IBar 
{ 
    public void DoBar(){ ... } 
} 

struct Quux : IBar 
{ 
    public void DoBar(){ ... } 
} 

現在,如果我做了以下內容:

public void ExecuteBar<T>(T bar) where T:IBar 
{ 
    bar.DoBar(); 
} 

ExecuteBar(new Baz()); 
ExecuteBar(new Quux()); 

這是否會產生ExecuteBar兩個版本,每一個直接(非虛擬)直接撥打Bar.DoBar()Quux.DoBar()?或者是在運行時完成調度?

回答

4

這個問題沒有直接的答案,它很大程度上取決於您使用的抖動以及通用方法中存在哪種代碼。

一個起點是抖動確實爲每個單獨的值類型生成了一個獨特的方法。即使對於完全相同的結構也是如此。你可以用調試器看到的東西。使用Debug + Windows + Disassembly查看生成的機器碼。單步進入方法,使用Debug + Windows + Registers窗口,EIP/RIP寄存器顯示方法在內存中的位置。

但是,像這樣的通用方法仍然有資格進行內聯優化。對perf來說非常重要,它會使整個方法消失,並將方法中的代碼注入到調用方法中。在這種情況下,通用方法之間的區別是消失。這不是通常可以指望爲接口實現方法發生的事情。但是,如果將方法體留空,它會發生在您的示例代碼中。對於x86和x64抖動有不同的結果。

您只能通過查看生成的機器碼來真正地告訴您所得到的結果。確保允許優化器執行其工作,工具+選項,調試,常規,取消選中「取消JIT優化」複選框。當然,確保你的永不依賴於這個問題的準確答案。像這樣的實現細節如有更改,恕不另行通知。

+0

正確。所以我猜想,在提問者感興趣的層面上,答案是:前一種替代方法創建了兩種不同的方法,每種類型都有一種方法,並且調度是非虛擬的。 – 2014-09-01 22:48:13

+0

謝謝! @傑普:是的。可能是這種情況,代碼通過內聯進一步優化,而且效率更高,但我對結構體上的接口調用的基線性能模型感興趣,這些結構體的類型顯式作爲參數傳遞給泛型方法或類。我知道你不應該依賴這種行爲,但它可以作爲優化的起點。如果派遣正在減慢代碼速度,可能值得將類更改爲結構以消除派遣。 – Jules 2014-09-02 07:43:39

1

不直接使用通用定義。相反,構造類型是在運行時創建的。

將爲每個值類型參數創建一個構造。將爲所有引用類型參數創建一個獨特的構造。

Afaik,不同類型不共享相同的asm指令。

0

我把你的代碼放到LINQPad和產生IL表明,有ExcuteBar只有一個版本,它調用IBar.DoBar()

ExecuteBar: 
IL_0000: nop   
IL_0001: ldarga.s 01 
IL_0003: constrained. 01 00 00 1B 
IL_0009: callvirt UserQuery+IBar.DoBar 
IL_000E: nop   
IL_000F: ret 

方法ExecuteBar2(IBAR吧)長相相似:

ExecuteBar2: 
IL_0000: nop   
IL_0001: ldarg.1  
IL_0002: callvirt UserQuery+IBar.DoBar 
IL_0007: nop   
IL_0008: ret 

編輯:

class C : IBar { 
    public void DoBar(){} 
} 

參考類型被作爲參數傳遞到相同的方法(一個或多個)(Baz和Quux盒裝)

+0

在IL級別,當然。但請注意,限制前綴。我的問題是CLR用它做什麼,而不是C#編譯器用它做什麼。 – Jules 2014-09-01 18:41:30

相關問題