C# 性能优化最佳实践
原创
©著作权归作者所有:来自51CTO博客作者jiankunking的原创作品,请联系作者获取转载授权,否则将追究法律责任
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);
6、字符串操作:
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、避免不必要的抛出异常
C# 异常处理(Catch Throw)IL分析
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");
10、C# DataSet性能最佳实践
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倍以上
|
- 应尽量避免使用反射和动态绑定;如必须使用,要遵循以下原则:
1. 使用接口调用方式将动态绑定改造为早期绑定(Direct Call)
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
C# 序列化
C# Protobuf-Net 序列化
13、数据压缩
在数据从客户端传输到服务端过程,为减少数据传输量,建议对数据进行压缩处理。
常用的数据压缩: C# 文件流压缩解压
14、