2013-06-04 71 views
2

我正在建模一些類,以表示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倍的性能下降。 有人可以向我解釋這個(如果可能,建議一種方法來獲得更好的速度,而不訴諸代碼重複)?

+1

我不知道C#,但如果要我猜,它看起來像多態呼叫沒有被內聯。 – Mysticial

+0

@Mystical Aww男人,我希望情況並非如此。只是做了一個快速檢查,內聯函數(編譯器自己做的)僅在4.5以上,而我被困在4.0以下。 – KChaloux

+0

JIT編譯器可以總是內聯函數,這並不是4.5的新功能 - 只有請求激進內聯的能力是新的。 – harold

回答

3

對於第一次打擊,正如我在評論中所說的,我需要知道一些實現細節: ToInches()方法,ToThis()方法,Value屬性的類型或代碼。

對於第二性能比較命中,最可能的原因是使用新的()約束爲基類,並使用
TConcrete result = new TConcrete();

編譯器生成Activator.CreateInstance<TConcrete>()在這種情況下,該方法使用在引擎蓋下的反射和是慢。如果你有數百萬個實例,它肯定會降低性能。 Here你可以找到Jon Skeet的一些基準。

如果您使用> = 3.5框架,你可以使用表達式樹來構建對象的快速泛型實例中的基礎類:

private static Func<TConcrete> _createFunc; 

    private static TConcrete CreateNew() 
    { 
      if (_func == null) 
      { 
       _createFunc = Expression.Lambda<Func<TConcrete>>(Expression.New(typeof (TConcrete))).Compile(); 
      } 
      return _createFunc.Invoke(); 
    } 

    public TConcrete Add(IDistanceUnit unit) 
    { 
      TConcrete result = CreateNew(); 
      result.Value = Value + ToThis(unit).Value(); 
      return result; 
    } 
+0

不幸的是,我被卡在支持.NET 2.0之類的東西,但是對於new()使用Activator.CreateInstance這個事實的啓示真的很有幫助。謝謝。 – KChaloux