2016-04-30 43 views
1

我正在研究一個引擎,我們在運行時動態地複製大量和大量屬性。根據情況,我們可能會或可能不會修改一路上的房產價值。它最初是用反思寫的,但由於性能問題,我們最近在Reflection.Emit中重寫了它。重寫是完整的,性能顯然要好很多,但現在代碼正在與手寫的C#進行基準測試。顯然,爲了公平對抗,手寫的基準C#IL具有「相似的功能」(你會看到我的意思)。C#EMIT IL性能問題

一些IL引擎已被簽名,因爲它已經通過了飛行的色彩,並且與手寫的C#差不多是1:1。這告訴我:

  1. 存在調用動態方法沒有開銷

  2. 我們的總體思路和實施是正確的

  3. 基準是正確的

  4. IL和手寫C#是正在以完全相同的方式進行測試,所以沒有有趣的JIT業務正在進行(我不認爲)

我們期待IL比手寫的要慢一些,但目前情況並非如此。這可能會比較慢,但是你可以在IL中使用快捷鍵,這樣可以彌補差異。

在一個特定情況下,其實質上較慢。慢兩倍。

C#,你必須:

class Source 
{ 
    public string S1 { get; set; } 
    public int I1 { get; set; } 
    public int I2 { get; set; } 
    public double D1 { get; set; } 
    public double D2 { get; set; } 
    public double D3 { get; set; } 
} 

class Dest 
{ 
    public string S1 { get; set; } 
    public int I1 { get; set; } 
    public string I2 { get; set; } 
    public double D1 { get; set; } 
    public int D2 { get; set; } 
    public string D3 { get; set; } 
} 

static Dest Test(Source s) 
{ 
    Dest d = new Dest(); 

    object o = s.D3; 

    if (o != null) 
     d.D3 = o.ToString(); 

    return d; 
} 

這就是我的意思通過類似的功能。通常,當我們將一個屬性複製到一個字符串時,我們首先將它放入然後調用Object.ToString()。本質上,價值類型呼籲ToString不同,因此上面的代碼,蘋果蘋果。

如果我將D3複製/ ToString註釋掉並取消其他5個屬性的註釋,我將回到1:1並使用C#

你會發現,I2int - >string,但出於某種原因,一個不具有相同的問題,因爲與double - >string。我知道一般情況下雙ToString()更昂貴,但是這些花費也應該顯示在C#代碼中,但它不會。

我爲D3副本發出的代碼與我爲I2副本發出的代碼相同,爲什麼D3副本上的巨大開銷?

編輯:

編譯器發出:

IL_0000: newobj instance void ConsoleApplication3.Dest::.ctor() 
    IL_0005: ldarg.0 
    IL_0006: callvirt instance float64 ConsoleApplication3.Source::get_D3() 
    IL_000b: box [mscorlib]System.Double 
    IL_0010: stloc.0 
    IL_0011: dup 
    IL_0012: ldloc.0 
    IL_0013: brtrue.s IL_0018 

    IL_0015: ldnull 
    IL_0016: br.s IL_001e 

    IL_0018: ldloc.0 
    IL_0019: callvirt instance string [mscorlib]System.Object::ToString() 

    IL_001e: callvirt instance void ConsoleApplication3.Dest::set_D3(string) 
    IL_0023: ret 

我的代碼不發出新的目的地對象這種特殊的部分,這是在其他地方進行。 dup如上面的C#中所示,欺騙Dest對象。

LocalBuilder localBuilderObject = generator.DeclareLocal(_typeOfObject); 

Label labelNull = generator.DefineLabel(); 
Label labelNotNull = generator.DefineLabel(); 

generator.Emit(OpCodes.Ldarg_0); 
generator.Emit(OpCodes.Callvirt, miGetter); 
generator.Emit(OpCodes.Box, typeSource); 
generator.Emit(OpCodes.Stloc_S, localBuilderObject); 
generator.Emit(OpCodes.Dup); 
generator.Emit(OpCodes.Ldloc_S, localBuilderObject); 
generator.Emit(OpCodes.Brtrue, labelNotNull); 
generator.Emit(OpCodes.Ldnull); 
generator.Emit(OpCodes.Br, labelNull); 
generator.MarkLabel(labelNotNull); 
generator.Emit(OpCodes.Ldloc_S, localBuilderObject); 
generator.Emit(OpCodes.Callvirt, _miToString); 
generator.MarkLabel(labelNull); 
generator.Emit(OpCodes.Callvirt,miSetter); 

正如我所說,我中類型,所以我可以叫Object::ToString()一般不用擔心值類型。引用類型也會經過這條路徑。 C#代碼的行爲是這樣的,仍然需要1/2的時間?

我一直在整個週末都在討論這個問題。進一步的測試顯示其他值類型是1:1。 int,long等。由於某些原因,double正在導致問題。

+0

我發現你的代碼有點複雜:你加載_this_參數,獲取字段值,將其存儲,然後將其存儲在本地。那麼你重複什麼?此外,請包括您發佈的實際IL以及C#爲其版本生成的實際IL。 –

+0

@ 500-InternalServerError,謝謝你的迴應。我已經更新了原始帖子以擺脫IL包裝(儘管它不添加任何其他操作碼或任何東西)。重複項是Dest對象。一些差異在我的代碼:stloc_s/ldloc_s而不是stloc.0/ldloc.0和分支vs分支短褲。 Int/long與C#性能明智的是1:1,所以它只是雙倍的東西。 – SledgeHammer

+0

在這個微觀基準的層面上,愚蠢的東西,比如頁面上的代碼對齊,足以讓你動起來。您還必須深入查看由抖動生成的實際代碼,以查看是否存在實際差異;簡單地繼續盯着IL將無濟於事。 –

回答

0

跳過如果null(brfalse)而不是雙跳。您的基準可能是基於路(這裏不貼)3個原因,虛假你打電話給你生成的代碼:

  1. 你只能委託調用它(如果你打電話不這樣做,在其他生成的代碼)
  2. 您的常規代碼必須與代表一起調用才能進行比較。
  3. 委託給非靜態方法比靜態方法的委託構建要快(在真正的處理之前,clr會推空,跳過並彈出未使用的空值)。您必須生成一個帶有第一個未使用參數(引用類型)的靜態方法,並調用Delegate.CreateDelegate,並將target指定爲null,以防止目標被阻止。
0

正如你可以在C#編譯的代碼中看到,快速的本地訪問指令用於:

IL_000b: box [mscorlib]System.Double 
IL_0010: stloc.0 
IL_0011: dup 
IL_0012: ldloc.0 
... 
IL_0018: ldloc.0 

相反,在你IL生成的代碼,您使用stloc.sldloc.s也採取的操作數本地索引。

還要確保您緩存(如果C#的運行速度快兩倍,您可能會發生此問題)生成的方法每生成一個Type