静态方法比实例方法快吗?
我们总是从各个渠道听说:静态方法比实例方法要快,所以,我想亲自试试。测试方法很简单,循环调用实例方法和静态方法。
/// <summary> /// 这是一个普通类,调用实例的方法 /// </summary> public class C1 { public void DoLoop() { for ( int i = 0; i < int .MaxValue; i++) { DoIt(); } } private void DoIt() { } } /// <summary> /// 使用静态方法调用。 /// </summary> public static class C2 { public static void DoLoop() { for ( int i = 0; i < int .MaxValue; i++) { DoIt(); } } private static void DoIt() { } } |
测试结果如下:
测试多次,基本偏差不大,只能说,静态方法比实例方法快那么可怜的一点点,鉴于实例方法的灵活性远大于静态方法,所以还是一般使用实例方法吧。
也实验过,在方法中访问实例字段和静态字段,发现也没有区别,所以不再单独罗列代码。
避免方法内创建实例的情况
这个要讨论的问题有点难说明,我们还是先看一看.net内部的代码吧,下面是一段Collection<T>.Add的方法:
1
2
3
4
5
6
7
8
9
|
public void Add(T item) { if ( this .items.IsReadOnly) { ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); } int count = this .items.Count; this .InsertItem(count, item); } |
注意ThrowHelper类,如果换成我们自己写,一句话就搞定了:throw new NotSupportedException。为什么微软要这么写呢?
老外有解释:Why does SortedList implementation use ThrowHelper instead of throwing directly?
其实,我也是信奉此真理,而且就在前一段时间,一位同事还找我问,两段几乎一样的代码,为什么测试性能有差距,结果我按照此原理,将异常抛出放在外面,结果真的变好了。
现在,我还要再次测试一下,我相信的是数据:
class C1 { private Dictionary< int , int > _dict = new Dictionary< int , int >() ; public C1() { _dict.Add(1, 1); _dict.Add(2, 2); } public void Do1() { object obj = new object (); for ( int i = 0; i < int .MaxValue/100; i++) { GetItOne(1); } } //这个方法,在内部可能创建实例 private int GetItOne( int key) { int value; if (!_dict.TryGetValue(key, out value)) { throw new ArgumentOutOfRangeException( "key" ); } return value; } public void Do2() { for ( int i = 0; i < int .MaxValue/100; i++) { GetItTwo(1); } } //这个方法,将创建实例的代码移动到外部 private int GetItTwo( int key) { int value; if (!_dict.TryGetValue(key, out value)) { ThrowArgumentOutOfRangeException(); } return value; } private static void ThrowArgumentOutOfRangeException() { throw new ArgumentOutOfRangeException( "key" ); } } |
测试结果是:
基本上,会快0.06秒左右,但是如此大的循环得到的好处并不是那么的明显,但有作用。这种写法还是比较舒服的,所以还是建议大家用吧。
接上篇继续,本文的完整源代码也在上篇文章中。
枚举数组和普通枚举性能差异
有些人可能知道,.net在处理枚举时,对于数组有特别的优化,所以,当枚举的集合是一个数组时,性能会好些。例如下面的测试代码:
1 class C1 { 2 3 public void Do1() { 4 int[] array = { 1, 2, 3, 4 }; 5 for (int i = 0; i < int.MaxValue/100; i++) { 6 DoIt1(array); 7 } 8 } 9 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中,其实是有这样考虑的代码的。例如:
public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) { if (source == null) { throw Error.ArgumentNull("source"); } if (selector == null) { throw Error.ArgumentNull("selector"); } if (source is Enumerable.Iterator<TSource>) { return ((Enumerable.Iterator<TSource>)source).Select<TResult>(selector); } if (source is TSource[]) { return new Enumerable.WhereSelectArrayIterator<TSource, TResult>((TSource[])source, null, selector); } if (source is List<TSource>) { return new Enumerable.WhereSelectListIterator<TSource, TResult>((List<TSource>)source, null, selector); } return new Enumerable.WhereSelectEnumerableIterator<TSource, TResult>(source, null, selector); }
创建类和结构的性能差异以及属性和字段的性能差异
下面我还要试验创建类实例和结构类型,其性能差异到底有多大?会不会.net的垃圾回收超级厉害,基本上差异不大呢?当然,我也顺手测试了访问属性和访问字段的差别。
1 class C1 { 2 public void Do1() { 3 for (int i = 0; i < int.MaxValue/10; i++) { 4 var p = new PointClass() { X = 1, Y = 2 }; 5 } 6 } 7 public void Do2() { 8 for (int i = 0; i < int.MaxValue/10 ; i++) { 9 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 { get; set; } 27 public int Y { get; set; } 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 }