2009-12-21 77 views
1

使用C#時,我注意到使用動態生成類型的實例與簡單結構填充列表時的性能差異。下面的代碼包含4種用於使用100,000個對象填充列表的不同方法。動態創建類型的性能

每種方法進行不同的:

Button1的:15毫秒

將Button2:31毫秒

將Button3 & 4:300毫秒

注意,對於按鈕3的代碼& 4從來到this topic

任何人都可以解釋爲什麼動態創建的對象更慢?

public struct DataRow 
    { 
     public double t; 
     public double vf; 
     public double im; 

     public double T { get { return t; } set { t = value; } } 
     public double Vf { get { return vf; } set { vf = value; } } 
     public double Im { get { return im; } set { im = value; } } 
    } 

    //Use struct defined above 
    private void button1_Click(object sender, EventArgs e) 
    { 
     int n = 0; 

     //adding rows 
     List<DataRow> myTable = new List<DataRow>(); 
     DataRow myRow = new DataRow(); 

     start = DateTime.Now; 

     while (n < 100000) 
     { 
      myRow.T = n * 1.0; 
      myRow.Vf = 2.0; 
      myRow.Im = 4.0; 

      myTable.Add(myRow); 

      n++; 
     } 
     end = DateTime.Now; 
     System.TimeSpan diff = end.Subtract(start); 
     label2.Text = diff.Seconds.ToString(); 
     label4.Text = diff.Milliseconds.ToString(); 

     dataGridView1.DataSource = myTable; 
    } 

    //define the list as it is done on buttons 3 & 4 but use the static struct 
    private void button2_Click(object sender, EventArgs e) 
    { 
     Type myType = typeof(DataRow); 

     Type listType = typeof(List<>); 

     Type myListType = listType.MakeGenericType(myType); 

     IList myTable = (IList)Activator.CreateInstance(myListType); 

     DataRow bRow = new DataRow(); 

     int n = 0; 
     start = DateTime.Now; 
     while (n < 100000) 
     { 
      bRow.t = n * 1.0; 
      bRow.vf = 2.0; 
      bRow.im = 4.0; 
      myTable.Add(bRow); 

      n++; 
     } 
     end = DateTime.Now; 
     System.TimeSpan diff = end.Subtract(start); 
     label2.Text = diff.Seconds.ToString(); 
     label4.Text = diff.Milliseconds.ToString(); 
     dataGridView1.DataSource = myTable; 

    } 

    //Create assy at runtime and load dll 
    private void button3_Click(object sender, EventArgs e) 
    { 
     Type myType = CreateDynRow(); 
     Assembly myAssy = Assembly.LoadFrom("DynaRowAssy.dll"); 
     Type myRow = myAssy.GetType("DynaRow"); 

     Type listType = typeof(List<>); 

     Type myListType = listType.MakeGenericType(myRow); 

     IList myTable = (IList)Activator.CreateInstance(myListType); 

     FieldInfo piT = myRow.GetField("t"); 
     FieldInfo piVf = myRow.GetField("vf"); 
     FieldInfo piIm = myRow.GetField("im"); 

     ValueType aRow = (ValueType)Activator.CreateInstance(myRow); 

     int n = 0; 
     start = DateTime.Now; 
     while (n < 100000) 
     { 
      piT.SetValue(aRow, 1 * n); 
      piVf.SetValue(aRow, 2.0); 
      piIm.SetValue(aRow, 4.0); 
      myTable.Add(aRow); 

      n++; 
     } 
     end = DateTime.Now; 
     System.TimeSpan diff = end.Subtract(start); 
     label2.Text = diff.Seconds.ToString(); 
     label4.Text = diff.Milliseconds.ToString(); 
     dataGridView1.DataSource = myTable; 
    } 

    //create assy at runtime in memory 
    private void button4_Click(object sender, EventArgs e) 
    { 
     //build the assembly 
     Type myType = CreateDynRow(); 

     Type listType = typeof(List<>); 

     Type myListType = listType.MakeGenericType(myType); 

     IList myTable = (IList)Activator.CreateInstance(myListType); 

     FieldInfo piT = myType.GetField("t"); 
     FieldInfo piVf = myType.GetField("vf"); 
     FieldInfo piIm = myType.GetField("im"); 

     ValueType aRow = (ValueType)Activator.CreateInstance(myType); 

     int n = 0; 
     start = DateTime.Now; 
     while (n < 100000) 
     { 
      piT.SetValue(aRow, 1 * n); 
      piVf.SetValue(aRow, 2.0); 
      piIm.SetValue(aRow, 4.0); 
      myTable.Add(aRow); 

      n++; 
     } 
     end = DateTime.Now; 
     System.TimeSpan diff = end.Subtract(start); 
     label2.Text = diff.Seconds.ToString(); 
     label4.Text = diff.Milliseconds.ToString(); 
     dataGridView1.DataSource = myTable; 
    } 
+5

只是一個提示:DateTime的分辨率大約是15毫秒,所以當你使用DateTime.Now來計時的時候,兩個實際上執行得非常相似的事情可能會出現不同的長達15ms,只是由於舍入的略有不同邊界。因此,如果您的基準測試在15-30ms左右運行,則要麼運行更多迭代(以便15ms舍入誤差變得微不足道),要麼使用System.Diagnostics.Stopwatch等更高分辨率的計時器(後者稍微更方便一些API也是!)。 – itowlson 2009-12-21 19:43:10

回答

5

這不是(主要)動態創建。它是利用反射(FieldInfo.SetValue)才使得時相比,呼叫可以被編譯慢BUTTON3和將Button4版本

對此的一種可能的方式是聲明你的代碼可以編譯的接口,並且有動態類實現這個接口。您仍然會通過反射實例化並查詢該接口的動態類型,但在此之後它應該與「靜態」引用一樣快。

+2

你也可以使用'System.Expressions'來解決這個問題,所以你只需要爲整個對象做一次反射。 – 2009-12-21 19:56:10

+1

另外,如果您要使用反射並進行多次重複調用,請考慮創建一個委託並使用它。 http://msmvps.com/blogs/jon_skeet/archive/2008/08/09/making-reflection-fly-and-exploring-delegates.aspx – 2009-12-22 17:28:15

1

簡單的答案:正在執行更多的代碼dynamicaly創建對象。

反射總是比預先定義類型慢,然後直接使用該對象。你要求運行時爲你做更多的工作,而不是事先指定一切。根據您使用的反射功能,您的代碼會變慢。

如果您需要具體細節,請檢查您的代碼生成的IL。這應該給你整個故事。

0

這是我們想出的。它幾乎與靜態定義的情況一樣快:

// Dynamically create DataRow derived from ValueType, 
// List of DataRows, 
// Delegates to properties 
// 

private void button4_Click(object sender, EventArgs e) 
{ 
    Type myType = CreateDynRow(); // dynamic version of DataRow, see above 
    Type myListType = typeof(List<>).MakeGenericType(myType); 
    IList myTable = (IList)Activator.CreateInstance(myListType); 
    ValueType myRowBuffer = (ValueType)Activator.CreateInstance(myType); 

    var mySet_TDelegate = myRowBuffer.GetInstanceInvoker<Action<Double>>("set_T"); 
    var mySet_ImDelegate = myRowBuffer.GetInstanceInvoker<Action<Double>>("set_Im"); 
    var mySet_VfDelegate = myRowBuffer.GetInstanceInvoker<Action<Double>>("set_Vf"); 

    stopWatch.Reset(); 
    stopWatch.Start(); 
    for (int n = 0; n < rowCount; n++) 
    { 
     mySet_TDelegate(1.0 * n); 
     mySet_ImDelegate(4.0); 
     mySet_VfDelegate(2.0); 

     myTable.Add(myRowBuffer); 
    } 
    stopWatch.Stop(); 
    label1.Text = String.Format("{0}", stopWatch.ElapsedMilliseconds); 

    dataGridView1.DataSource = myTable; 
} 

感謝您的幫助。順便說一下,我們使用Justin的&馬克的答案來到這裏。 GetInstanceInvoker來自Kenneth Xu的Common.Reflection類。