我正在建模一些類,以表示C#中的度量單位。例如,我有毫米和英寸建模,有一個IDistanceUnit
接口和一個基類DistanceUnit
類提供了常見的實現細節。Polymorphic Performance Hit
有基本運算功能,其定義爲這樣,在每個具體類的:不同值的
public class Inches :
DistanceUnit<Inches>,
IDistanceUnit,
INumericUnit<Inches, IDistanceUnit>
{
// ... snip ...
public Inches Add(IDistanceUnit unit)
{
return new Inches(Value + unit.ToInches().Value);
}
// ... snip ...
}
標杆10,000,000增加轉換爲英寸和毫米具有小但可接受性能命中。手動執行轉換的原始雙打大約需要70-100毫秒,其中類需要大約700-1000毫秒。
我可以抽象的細節,下入DistanceUnit
基類和刪除一些不必要的重複:
public abstract class DistanceUnit<TConcrete>
IDistanceUnit,
INumericUnit<TConcrete, IDistanceUnit>
where TConcrete :
IDistanceUnit,
INumericUnit<TConcrete, IDistanceUnit>,
new()
{
// ... snip ...
public TConcrete Add(IDistanceUnit unit)
{
TConcrete result = new TConcrete();
reuslt.Value = Value + ToThis(unit).Value();
return result;
}
// ... snip ...
}
// ToThis() calls the correct "ToXYZ()" for the current implementing class
這滴通過至少另一個因素我的表現。我看到大約8000毫秒,僅僅是將實現移到基類中,而不是800毫秒。
我已經排除了從手動測試的幾件事情:
- 開關使用
ToThis(IDistanceUnit)
沒有顯示出明顯的 性能損失的具體類,而不是直接 調用「ToInches使用時「或」ToMillimeters「 - 使用無參數構造函數創建對象(
new TConcrete()
)並指定 後面的值在最壞情況下會有幾毫秒的性能超過10,000,000個 cal culations。
我用下面的代碼進行基準測試我的結果:
int size = 10000000;
double[] mms = new double[size];
double[] inches = new double[size];
// Fill in some arbitrary test values
for (int i = 0; i < size; i++)
{
mms[i] = i;
inches[i] = i;
}
Benchmark("Manual Conversion",() =>
{
for (int i = 0; i < size; i++)
{
var result = mms[i] + (inches[i] * 25.4);
}
});
Benchmark("Unit Classes",() =>
{
for (int i = 0; i < size; i++)
{
var result = (new Millimeters(mms[i])) + (new Inches(inches[i]));
}
}
如果基準只是調用所提供的行動圍繞一個秒錶,並打印出以毫秒爲單位經過的時間。
唯一看起來造成主要區別的是Add
函數從具體類到抽象基類的移動,但我不明白爲什麼我會有近10倍的性能下降。 有人可以向我解釋這個(如果可能,建議一種方法來獲得更好的速度,而不訴諸代碼重複)?
我不知道C#,但如果要我猜,它看起來像多態呼叫沒有被內聯。 – Mysticial
@Mystical Aww男人,我希望情況並非如此。只是做了一個快速檢查,內聯函數(編譯器自己做的)僅在4.5以上,而我被困在4.0以下。 – KChaloux
JIT編譯器可以總是內聯函數,這並不是4.5的新功能 - 只有請求激進內聯的能力是新的。 – harold