C# 性能优化之斤斤计较

来源:互联网 发布:菜鸟网络会员 编辑:程序博客网 时间:2024/04/26 08:26

今天,我想跟大家聊一聊C#的性能优化,当然,这里并不谈基本的原则,这些都假设你已经非常精通了,本文聊的是要争取几个毫秒的程序。关于基本的性能优化,可以参考园子里的文章。比如:

.NET 性能优化方法总结

先说说我的测试环境:

 

\
 

一台典型的笔记本电脑,Windows 7中文版,.net Framework用的是4.5版本,VS是现在VS11 beta版。我也是用VS2008这样的环境测试了下面的所有场景,发现没有任何区别,所以就以VS11为基准了。

所有测试数据都是编译为Relase,且不包含PDB,直接双击运行而非在VS环境下执行。点击这里下载源代码。

言归正传,先测试第一点:

静态方法比实例方法快吗?
我们总是从各个渠道听说:静态方法比实例方法要快,所以,我想亲自试试。测试方法很简单,循环调用实例方法和静态方法。

 

view sourceprint?
01./// <summary>
02./// 这是一个普通类,调用实例的方法
03./// </summary>
04.public class C1 {
05.public void DoLoop() {
06.for (int i = 0; i < int.MaxValue; i++) {
07.DoIt();
08.
09.}
10. 
11.private void DoIt() {
12.}
13.}
14. 
15./// <summary>
16./// 使用静态方法调用。
17./// </summary>
18.public static class C2 {
19.public static void DoLoop() {
20.for (int i = 0; i < int.MaxValue; i++) {
21.DoIt();
22.}
23.}
24. 
25.private static void DoIt() {
26.}
27.}

测试结果如下:

 

\
 

测试多次,基本偏差不大,只能说,静态方法比实例方法快那么可怜的一点点,鉴于实例方法的灵活性远大于静态方法,所以还是一般使用实例方法吧。

也实验过,在方法中访问实例字段和静态字段,发现也没有区别,所以不再单独罗列代码。

避免方法内创建实例的情况
这个要讨论的问题有点难说明,我们还是先看一看.net内部的代码吧,下面是一段Collection<T>.Add的方法:

 

view sourceprint?
01.public void Add(T item)
02.{
03.if (this.items.IsReadOnly)
04.{
05.ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
06.}
07.int count = this.items.Count;
08.this.InsertItem(count, item);
09.}

注意ThrowHelper类,如果换成我们自己写,一句话就搞定了:throw new NotSupportedException。为什么微软要这么写呢?

老外有解释:Why does SortedList implementation use ThrowHelper instead of throwing directly?

其实,我也是信奉此真理,而且就在前一段时间,一位同事还找我问,两段几乎一样的代码,为什么测试性能有差距,结果我按照此原理,将异常抛出放在外面,结果真的变好了。

现在,我还要再次测试一下,我相信的是数据:

 

view sourceprint?
01.class C1 {
02.private Dictionary<intint> _dict = new Dictionary<intint>() ;
03.public C1() {
04._dict.Add(1, 1);
05._dict.Add(2, 2);
06.}
07. 
08.public void Do1() {
09.object obj = new object();
10.for (int i = 0; i < int.MaxValue/100; i++) {
11.GetItOne(1);
12.}
13.}
14. 
15.//这个方法,在内部可能创建实例
16.private int GetItOne(int key) {
17.int value;
18.if (!_dict.TryGetValue(key,out value)) {
19.throw new ArgumentOutOfRangeException("key");
20.}
21.return value;
22.}
23. 
24.public void Do2() {
25.for (int i = 0; i < int.MaxValue/100; i++) {
26.GetItTwo(1);
27.}
28.}
29. 
30.//这个方法,将创建实例的代码移动到外部
31.private int GetItTwo(int key) {
32.int value;
33.if (!_dict.TryGetValue(key, out value)) {
34.ThrowArgumentOutOfRangeException();
35.}
36.return value;
37.}
38. 
39.private static void ThrowArgumentOutOfRangeException() {
40.throw new ArgumentOutOfRangeException("key");
41.}
42.}

测试结果是:

 

\
 

基本上,会快0.06秒左右,但是如此大的循环得到的好处并不是那么的明显,但有作用。这种写法还是比较舒服的,所以还是建议大家用吧。

枚举数组和普通枚举性能差异
有些人可能知道,.net在处理枚举时,对于数组有特别的优化,所以,当枚举的集合是一个数组时,性能会好些。例如下面的测试代码:

 

view sourceprint?
01.class C1 {
02. 
03.public void Do1() {
04.int[] array = { 1, 2, 3, 4 };
05.for (int i = 0; i < int.MaxValue/100; i++) {
06.DoIt1(array);
07.}
08.}
09. 
10.private void DoIt1<T>(IEnumerable<T> array) {
11.foreach (var item in array) {
12. 
13.}
14.}
15. 
16.public void Do2() {
17.int[] array = { 1, 2, 3, 4 };
18.for (int i = 0; i < int.MaxValue/100; i++) {
19.DoIt2(array);
20.}
21.}
22. 
23.private void DoIt2(int[] array) {
24.foreach (var item in array) {
25. 
26.}
27.}
28.}

第23行的方法中,编译器提前已知是一个数组的枚举,所以会优化指令。那么,到底这种优化差距有多大呢?我需要试验一下。

 

\
 

第一个是Do1的结果,第二个是Do2的结果,显而易见,差距还是相当大的,为什么呢?从反编译的IL代码来看,第一个方法使用标准的GetEnumerator机制,需要创建实例,而且要调用Current和MoveNext两个方法,更何况,Array的GetValue实现实在不够快。而第二个方法使用了for的机制,无需创建实例,不断累加和一个判断语句即可,性能当然高了。

在Linq to Object中,其实是有这样考虑的代码的。例如:

 

view sourceprint?
01.public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
02.{
03.if (source == null)
04.{
05.throw Error.ArgumentNull("source");
06.}
07.if (selector == null)
08.{
09.throw Error.ArgumentNull("selector");
10.}
11.if (source is Enumerable.Iterator<TSource>)
12.{
13.return ((Enumerable.Iterator<TSource>)source).Select<TResult>(selector);
14.}
15.if (source is TSource[])
16.{
17.return new Enumerable.WhereSelectArrayIterator<TSource, TResult>((TSource[])source, null, selector);
18.}
19.if (source is List<TSource>)
20.{
21.return new Enumerable.WhereSelectListIterator<TSource, TResult>((List<TSource>)source, null, selector);
22.}
23.return new Enumerable.WhereSelectEnumerableIterator<TSource, TResult>(source, null, selector);
24.}

创建类和结构的性能差异以及属性和字段的性能差异

下面我还要试验创建类实例和结构类型,其性能差异到底有多大?会不会.net的垃圾回收超级厉害,基本上差异不大呢?当然,我也顺手测试了访问属性和访问字段的差别。

 

view sourceprint?
01.class C1 {
02.public void Do1() {
03.for (int i = 0; i < int.MaxValue/10; i++) {
04.var p = new PointClass() { X = 1, Y = 2 };
05.}
06.}
07.public void Do2() {
08.for (int i = 0; i < int.MaxValue/10 ; i++) {
09.var p = new PointClass2() { X = 1, Y = 2 };
10.}
11.}
12.public void Do3() {
13.for (int i = 0; i < int.MaxValue/10; i++) {
14.var p = new Point() { X = 1, Y = 2 };
15.}
16.}
17.public void Do4() {
18.for (int i = 0; i < int.MaxValue / 10; i++) {
19.var p = new Point() { XP = 1, YP = 2 };
20.}
21.}
22.}
23. 
24. 
25.class PointClass {
26.public int X { getset; }
27.public int Y { getset; }
28.}
29. 
30.class PointClass2 {
31.public int X;
32.public int Y;
33.}
34. 
35.struct Point {
36.public int X;
37.public int Y;
38. 
39.public int XP { get return X; } set { X = value; } }
40.public int YP { get return Y; } set { Y = value; } }
41.}

测试结果如下:

 

\


0 0