583
王者榮耀
C# 性能優化最佳實踐
1、顯式注冊的EvenHandler要顯式注銷以避免內存泄漏
將一個成員方法注冊到某個對象的事件會造成後者持有前者的引用。在事件注銷之前,前者不會被垃圾回收。
private void Form1_Load() { …… //注冊事件 CommandRemotingContext.CmdChanged += new ReciverCmdStateChangedEventHandler(this.CommandRemotingContext_CmdChanged); …… } private void Form1_FromClosed() { …… //關閉窗體時及時釋放事件 CommandRemotingContext.CmdChanged -= new ReciverCmdStateChangedEventHandler(this.CommandRemotingContext_CmdChanged); …… }
由事件引起的內存泄漏問題:
- 對象A訂閱了對象B中的事件
- 對象A的生命周期遠遠大於對象B
- 對象A沒有取消訂閱對象B的時間
- 最終導致對象B無法釋放
2、控件綁定的數據源批量操作應避免自動刷新
- 客戶端批量操作數據時,控件自帶的刷新操作,會造成不必要的時間消耗
-
當數據源(如DataTable、Array、List、ObservableCollection或其他IListSource等)被綁定到控件時,批量操作數據時應該斷開綁定或掛起控件的刷新。
this.gcBillList.DataSource = null; DataRowCollection rows = this.ds.Tables[0].Rows; foreach (DataRow row in rows) { // DataRow數據操作 } this.gcBillList.DataSource = this.ds.Tables[0].DefaultView;
3、減少客戶端與服務端的通信次數
- WebService調用並非越少越好,傳輸數據量較大的情況可考慮拆分為多次調用
-
對於短WebService的調用,應盡量合並以減少交互次數
//多次調用了相同的WS txtCompanyName.Text=SCPubFunctionClient.PublicWSCal<string>(“ForTest”, “GetCompanyNameByID”,“0001”); txtCompanyInnerName.Text=SCPubFunctionClient.PublicWSCal<string>(“ForTest”, “GetCompanyInnerNameByID”,“0001”); //合並相鄰的WS string[] result=SCPubFunctionClient.PublicWSCal<string>(“ForTest”, “GetCompanyNameAndInnerNameByID”,“0001”); txtCompanyName.Text=result[0]; txtCompanyInnerName.Text= result[1];
4、減少客戶端與服務端的通信次數
如非必要,應盡量避免在循環體內重複調用WebService
//循環調用了相同的WS List<Person> persons; …… foreach(string personID in personIDs) { person=HRPubWsClient.getPerson(personID); persons.Add(person); } //合並WS List<Person> persons; …… persons =HRPubWsClient.getPersonList(personIDs);5、使用泛型來避免裝箱、拆箱操作(減少垃圾回收壓力)
- 裝箱操作會造成GC壓力;如果發生在集合中,應該使用泛型集合避免。
-
對於值類型的集合,使用List<T>來代替ArrayList,使用Dictionary<TKey, TValue> 來代替Hashtable。
ArrayList h=new ArrayList(); //不建議 h.Add(1); List<object> h = new List<object>(); //不建議 h.Add(1); List<int> h = new List<int>(); //建議 h.Add(1);
C# 字符串操作--減少垃圾回收壓力
7、使用常量避免創建對象
-
如下例,程序中存在大量 new decimal(0)的代碼,這會導致小對象頻繁創建及回收;正確的做法是使用 Decimal.Zero 常量。
private string CurrencyCalc() { if (firstValue == new decimal(0)) …… if (secondValue == new decimal(0)) …… if (thirdValue == new decimal(0)) …… if (fourthValue == new decimal(0)) …… …… }
8、避免不必要的拋出異常
9、使用RemoveAll而非RemoveAt進行刪除多個元素
-
使用RemoveAll方法對集合(如List)中的多個元素進行一次性刪除時,隻會對List的內部數組做一次resize 操作,效率明顯高於循環調用RemoveAt。
List<string> lst = new List<string> {"1", "2", "3", "1", "2", "4"}; //不建議: for (int i = lst.Count - 1; i >= 0; i--) { if (lst[i] == "1" || lst[i] == "2") { lst.RemoveAt(i); } } //建議: lst.RemoveAll(s => s == "1" || s == "2");
11、反射與動態綁定--減少CPU占用
- 反射技術是將編譯期間的靜態綁定轉換為延遲到運行期間的動態綁定。
- C#主要支持 5 種動態創建對象的方式(時間消耗來自網絡,與我實測的差距挺大,具體測試見下方):
動態創建對象的方式 |
與Direct Create |
1.Type.InvokeMember |
慢40倍以上 |
2.ContructorInfo.Invoke |
慢40倍以上 |
3.Activator.CreateInstance(Type) |
慢7倍 |
4.Activator.CreateInstance(assemblyName, typeName) |
慢1000倍以上 |
5.Assembly.CreateInstance(typeName) |
慢40倍以上 |
- 應盡量避免使用反射和動態綁定;如必須使用,要遵循以下原則:
2. 使用 Activator.CreateInstance(Type)方式動態創建對象
3. 使用typeof操作符代替GetType調用
小注:
通過循環創建實例記錄時間如下:
加載程序集、獲取類型在循環外部時間如下(這時不同創建方式消耗時間差距挺大):
代碼如下:
public void TestCreateInstance() { Stopwatch watch1 = new Stopwatch(); var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll"); Type type = asmb.GetType("ReflectiveClassLibrary.TestClass"); int num = 100000; watch1.Start(); for (int i = 0; i < num; i++) { //var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll"); //Type type = asmb.GetType("ReflectiveClassLibrary.TestClass"); Activator.CreateInstance(type); } watch1.Stop(); label1.Text = "Activator.CreateInstance(Type type)時間:" + watch1.ElapsedMilliseconds + "毫秒"; watch1.Reset(); watch1.Start(); for (int i = 0; i < num; i++) { Activator.CreateInstance("ReflectiveClassLibrary", "ReflectiveClassLibrary.TestClass"); } watch1.Stop(); label2.Text = "Activator.CreateInstance(string assemblyName,string typeName)時間:" + watch1.ElapsedMilliseconds + "毫秒"; watch1.Reset(); watch1.Start(); for (int i = 0; i < num; i++) { //var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll"); //加載程序集 asmb.CreateInstance("TestClass"); } watch1.Stop(); label3.Text = "assembly.CreateInstance(string typeName)時間:" + watch1.ElapsedMilliseconds + "毫秒"; watch1.Reset(); watch1.Start(); for (int i = 0; i < num; i++) { //var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll"); //Type type = asmb.GetType("ReflectiveClassLibrary.TestClass"); object obj = type.InvokeMember(null, BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, null, null, null); } watch1.Stop(); label4.Text = "Type.InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args)時間:" + watch1.ElapsedMilliseconds + "毫秒"; watch1.Reset(); watch1.Start(); for (int i = 0; i < num; i++) { //var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll"); //Type type = asmb.GetType("ReflectiveClassLibrary.TestClass"); ConstructorInfo constructorInfo = type.GetConstructors()[0]; constructorInfo.Invoke(null); } watch1.Stop(); label5.Text = "ContructorInfo.Invoke(object[] parameters)時間:" + watch1.ElapsedMilliseconds + "毫秒"; }
加載程序集、獲取類型在循環內部時間如下(這時不同創建方式消耗時間差距比較小):
代碼如下:
public void TestCreateInstance() { Stopwatch watch1 = new Stopwatch(); //var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll"); //Type type = asmb.GetType("ReflectiveClassLibrary.TestClass"); int num = 100000; watch1.Start(); for (int i = 0; i < num; i++) { var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll"); Type type = asmb.GetType("ReflectiveClassLibrary.TestClass"); Activator.CreateInstance(type); } watch1.Stop(); label1.Text = "Activator.CreateInstance(Type type)時間:" + watch1.ElapsedMilliseconds + "毫秒"; watch1.Reset(); watch1.Start(); for (int i = 0; i < num; i++) { Activator.CreateInstance("ReflectiveClassLibrary", "ReflectiveClassLibrary.TestClass"); } watch1.Stop(); label2.Text = "Activator.CreateInstance(string assemblyName,string typeName)時間:" + watch1.ElapsedMilliseconds + "毫秒"; watch1.Reset(); watch1.Start(); for (int i = 0; i < num; i++) { var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll"); //加載程序集 asmb.CreateInstance("TestClass"); } watch1.Stop(); label3.Text = "assembly.CreateInstance(string typeName)時間:" + watch1.ElapsedMilliseconds + "毫秒"; watch1.Reset(); watch1.Start(); for (int i = 0; i < num; i++) { var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll"); Type type = asmb.GetType("ReflectiveClassLibrary.TestClass"); object obj = type.InvokeMember(null, BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, null, null, null); } watch1.Stop(); label4.Text = "Type.InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args)時間:" + watch1.ElapsedMilliseconds + "毫秒"; watch1.Reset(); watch1.Start(); for (int i = 0; i < num; i++) { var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll"); Type type = asmb.GetType("ReflectiveClassLibrary.TestClass"); ConstructorInfo constructorInfo = type.GetConstructors()[0]; constructorInfo.Invoke(null); } watch1.Stop(); label5.Text = "ContructorInfo.Invoke(object[] parameters)時間:" + watch1.ElapsedMilliseconds + "毫秒"; }
測試代碼如下:c# 反射測試demo
12、序列化與反序列化
- 相對於XML、二進製序列化方式,Protobuf效率較高,支持數據量較大
- protobuf序列化後的大小是json的1/10,xml格式的1/20,是二進製序列化的1/10
13、數據壓縮
在數據從客戶端傳輸到服務端過程,為減少數據傳輸量,建議對數據進行壓縮處理。
常用的數據壓縮: C# 文件流壓縮解壓
14、
最後更新:2017-04-03 12:54:45