我正在研究一個引擎,我們在運行時動態地複製大量和大量屬性。根據情況,我們可能會或可能不會修改一路上的房產價值。它最初是用反思寫的,但由於性能問題,我們最近在Reflection.Emit
中重寫了它。重寫是完整的,性能顯然要好很多,但現在代碼正在與手寫的C#
進行基準測試。顯然,爲了公平對抗,手寫的基準C#
與IL
具有「相似的功能」(你會看到我的意思)。C#EMIT IL性能問題
一些IL
引擎已被簽名,因爲它已經通過了飛行的色彩,並且與手寫的C#
差不多是1:1。這告訴我:
存在調用動態方法沒有開銷
我們的總體思路和實施是正確的
基準是正確的
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#
。
你會發現,I2
是int
- >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
正在導致問題。
我發現你的代碼有點複雜:你加載_this_參數,獲取字段值,將其存儲,然後將其存儲在本地。那麼你重複什麼?此外,請包括您發佈的實際IL以及C#爲其版本生成的實際IL。 –
@ 500-InternalServerError,謝謝你的迴應。我已經更新了原始帖子以擺脫IL包裝(儘管它不添加任何其他操作碼或任何東西)。重複項是Dest對象。一些差異在我的代碼:stloc_s/ldloc_s而不是stloc.0/ldloc.0和分支vs分支短褲。 Int/long與C#性能明智的是1:1,所以它只是雙倍的東西。 – SledgeHammer
在這個微觀基準的層面上,愚蠢的東西,比如頁面上的代碼對齊,足以讓你動起來。您還必須深入查看由抖動生成的實際代碼,以查看是否存在實際差異;簡單地繼續盯着IL將無濟於事。 –