昨天有朋友写了一篇文章,其中比较了List
注意事项
在后面的评论中有人说,List
那篇文章中得到的结果是不对的,那么问题究竟出在什么地方呢?在我看来,问题主要出在以下两点。
首先,原文作者使用了ASP.NET作为测试环境。值得注意的是,ASP.NET执行.NET代码的时候,使用的是IIS进程中托管的CLR,它的配置和直接运行.NET应用程序时不同(不同的CLR托管方式配置很可能不一样——例如SQL Server里托管的CLR)。例如,ASP.NET中每个线程的调用栈为250K,而直接运行.NET应用程序时这个大小为1M。根据我的经验(也就是说我无法确切地“证明”这个说法),在ASP.NET中执行此类性能测试得到的结果可能很不稳定。因此,一般我建议使用一个最普通的Console应用程序来进行性能测试。
其次,也是更重要的原因,便是原作者只测试了一次排序的性能。这是性能测试中的大忌讳,因为这么做的话误差实在太大。例如,会不会在进行某一个方法的测试时,忽然系统起了一个后台进程进行维护,动用了一部分CPU和内存资源,从而导致测试消耗的时间很冤枉地增加。而且,.NET程序是有一个“预热”过程的,这导致代码在第一次执行时需要有一个生成本机代码的过程(俗称“预热”)。这个过程和代码的执行效率是无关的,因为它无论如何只会产生一次消耗,而代码是会被执行无数次的。因此,在进行测试的时候,一定要将测试目标反复执行N次,至少要让执行耗时到达“秒”级别,这样的结果才有一定参考价值。如果执行时间太少的话测试也可能不准确——要知道“计时器”也是有开销,也是有误差的,我们要得到尽量准确的结果。
最后,我强烈建议使用CodeTimer进行计时,因为在它的Initialize方法中会将当前进程及线程的优先级设置到最高,以此减少其他无关因素所造成的干扰。如果可以的话,其实也应该尽量关闭系统中其他应用程序或服务,并且最好可以断开网络(也是一个道理)。当然Release编译也是一定需要的。而且,如果您一定需要使用ASP.NET进行性能测试的话,也千万记得要在web.config中将
因此,我认为那篇文章中的测试结果是不准确的,参考价值很低。
重新测试
既然如此,我们来重新进行一次试验吧。不过我现在来比较的是Array.Sort
static T[] CloneArray(T[] source) { var dest = new T[source.Length]; Array.Copy(source, dest, source.Length); return dest; }
从试验结果上看,数组的复制操作应该并没有造成显著影响。还有便是,经过阅读List
Array.Sort
private class Int32Comparer : IComparer<int> { #region IComparerMembers public int Compare(int x, int y) { return x - y; } #endregion }
于是,两“类”重载的测试方法分别为:
static void SortWithCustomComparer(int[] array) { Array.Sort(array, new Int32Comparer()); } static void SortWithDelegate(int[] array) { Array.Sort(array, (x, y) => x - y); }
但是,我在这里还是再补充一个排序“方法”(原因以后再谈)。因为int类型是框架知道该如何比较的类型,因此我们其实也可以这么写:
static void SortWithDefaultComparer(int[] array) { Array.Sort(array, Comparer<int>.Default); }
最后,参加活动的自然还有LINQ排序:
static void SortWithLinq(int[] array) { var sorted = (from i in array orderby i select i).ToList(); }
这里我使用的是ToList方法而不是ToArray方法来生成新序列,这是因为ToList的方法性能更高一些,我还是想尽可能将关注点放在“排序”而不是其他方面。
比较代码如下:
static void Main(string[] args) { var random = new Random(DateTime.Now.Millisecond); var array = Enumerable.Repeat(0, 1000 * 500).Select(_ => random.Next()).ToArray(); CodeTimer.Initialize(); CodeTimer.Time("SortWithDefaultComparer", 100, () => SortWithDefaultComparer(CloneArray(array))); CodeTimer.Time("SortWithCustomComparer", 100, () => SortWithCustomComparer(CloneArray(array))); CodeTimer.Time("SortWithDelegate", 100, () => SortWithDelegate(CloneArray(array))); CodeTimer.Time("SortWithLinq", 100, () => SortWithLinq(CloneArray(array))); Console.ReadLine(); }
首先,我们生成一个拥有50万个随机元素的数组,以此进行排序方法的性能比较。每次比较的时候我们都使用CloneArray方法来生成一个新的数组。
试验结果
试验结果如下:
SortWithDefaultComparer Time Elapsed: 7,662ms Gen 0: 49 Gen 1: 49 Gen 2: 49 SortWithCustomComparer Time Elapsed: 13,847ms Gen 0: 49 Gen 1: 49 Gen 2: 49 SortWithDelegate Time Elapsed: 19,625ms Gen 0: 49 Gen 1: 49 Gen 2: 49 SortWithLinq Time Elapsed: 31,785ms Gen 0: 99 Gen 1: 99 Gen 2: 99
从结果上来,四种做法的性能区别还是非常明显的:使用Comparer
经过测试后发现,其实LINQ排序的性能并不会比Array.Sort
至于其中的原因,我们下次再来进行分析——这些方法的实现,尤其是LINQ排序的实现,似乎还是挺有趣的。
本文代码:http://gist.github.com/281857
相关文章
- 数组排序方法的性能比较(1):注意事项及试验
- 数组排序方法的性能比较(2):Array.Sort
实现分析 - 数组排序方法的性能比较(3):LINQ排序实现分析
- 数组排序方法的性能比较(4):LINQ方式的Array排序
- 数组排序方法的性能比较(5):对象大小与排序性能