C# Array.Count()比List.Count()慢得多
使用扩展方法C# Array.Count()比List.Count()慢得多,c#,.net,performance,linq,ienumerable,C#,.net,Performance,Linq,Ienumerable,使用扩展方法IEnumerable时,数组的速度至少是列表的两倍 Function Count() List<int> 2,299 int[] 6,903 编辑: 答案非常惊人,但我想补充一句。 实际上有两个等效块,只需测试 不同的集合接口类型,并使用它找到的任何一种 首先(如果有的话)。我不知道.NET实现是否测试了 ICollection或ICo
IEnumerable
时,数组的速度至少是列表的两倍
Function Count()
List<int> 2,299
int[] 6,903
编辑: 答案非常惊人,但我想补充一句。
实际上有两个等效块,只需测试 不同的集合接口类型,并使用它找到的任何一种 首先(如果有的话)。我不知道.NET实现是否测试了 ICollection或ICollection
Count()
Function ICollection<T> Cast ICollection Cast
List 1,268 1,738
Array 5,925 1,683
函数ICollection Cast ICollection Cast
名单12681738
阵列59251683
这是因为int[]
需要施法,而List
不需要。如果要使用Length属性,则结果将非常不同-大约比List.Count()
快10倍,原因是Enumerable.Count()
执行强制转换到ICollection
以从列表和数组中检索计数
使用此示例代码:
public static int Count<TSource>(IEnumerable<TSource> source)
{
ICollection<TSource> collection = source as ICollection<TSource>;
if (collection != null)
{
return 1; // collection.Count;
}
}
公共静态整数计数(IEnumerable源代码)
{
ICollection collection=源作为ICollection;
if(集合!=null)
{
返回1;//collection.Count;
}
}
您可以确定阵列的强制转换需要更长的时间,事实上,计数所花费的大部分时间都来自此强制转换:
Function Count()
List<int> 1,575
int[] 5,069
函数计数()
清单1575
int[]5069
关键可能是(我强调的)这句话:
在.NET Framework 2.0版中,Array类实现System.Collections.Generic.IList,
System.Collections.Generic.ICollection和
System.Collections.Generic.IEnumerable通用接口最新版本
实现在运行时提供给阵列,因此
文档生成工具不可见。因此,通用
接口不会出现在数组的声明语法中
类,并且没有接口成员的引用主题
只能通过将数组强制转换为泛型接口类型来访问
(显式接口实现)
32位评测分析(全部以毫秒为单位,仅感兴趣的位,禁用JIT内联):
当应用程序退出时,会创建一个
报告.tab
文件,该文件可以在Excel中使用。我发布这篇文章不是为了回答问题,而是为了提供一个更易于测试的环境
我复制了Enumerable.Count()
的实际实现,并更改了原始测试程序以使用它,因此人们可以在调试器中单步执行它
如果您运行下面代码的发布版本,您将获得与OP类似的计时
对于List
和int[]
而言,分配给is2
的第一次强制转换将为非空,因此将调用is2.Count
因此,差异似乎来自.Count
的内部实现
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
public const long Iterations = (long)1e8;
static void Main()
{
var list = new List<int>() { 1 };
var array = new int[1];
array[0] = 1;
var results = new Dictionary<string, TimeSpan>();
results.Add("int[]", Benchmark(array, Iterations));
results.Add("List<int>", Benchmark(list, Iterations));
Console.WriteLine("Function".PadRight(30) + "Count()");
foreach (var result in results)
{
Console.WriteLine("{0}{1}", result.Key.PadRight(30), Math.Round(result.Value.TotalSeconds, 3));
}
Console.ReadLine();
}
public static TimeSpan Benchmark(IEnumerable<int> source, long iterations)
{
var countWatch = new Stopwatch();
countWatch.Start();
for (long i = 0; i < iterations; i++) Count(source);
countWatch.Stop();
return countWatch.Elapsed;
}
public static int Count<TSource>(IEnumerable<TSource> source)
{
ICollection<TSource> is2 = source as ICollection<TSource>;
if (is2 != null)
return is2.Count; // This is executed for int[] AND List<int>.
ICollection is3 = source as ICollection;
if (is3 != null)
return is3.Count;
int num = 0;
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
num++;
}
return num;
}
}
}
以上代码给出了在调试器外部运行的版本生成的以下结果:
Array elapsed = 00:00:02.9586515
List elapsed = 00:00:00.6098578
在这一点上,我想“我们当然可以优化Count()
来识别T[]
并直接返回.Length
。因此我将Count()
的实现更改如下:
public static int Count<TSource>(IEnumerable<TSource> source)
{
var array = source as TSource[];
if (array != null) // Optimised for arrays.
return array.Length; // This is executed for int[]
ICollection<TSource> is2 = source as ICollection<TSource>;
if (is2 != null)
return is2.Count; // This is executed for List<int>.
ICollection is3 = source as ICollection;
if (is3 != null)
return is3.Count;
int num = 0;
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
num++;
}
return num;
}
公共静态整数计数(IEnumerable源代码)
{
var数组=源作为TSource[];
if(array!=null)//针对数组进行了优化。
return array.Length;//这是对int[]执行的
ICollection is2=源作为ICollection;
如果(is2!=null)
return is2.Count;//这是针对列表执行的。
ICollection is3=源作为ICollection;
如果(is3!=null)
返回值为3。计数;
int num=0;
使用(IEnumerator enumerator=source.GetEnumerator())
{
while(枚举数.MoveNext())
num++;
}
返回num;
}
值得注意的是,即使进行了此更改,阵列在我的系统上仍然较慢,尽管非阵列必须进行额外的强制转换
我的结果(发布版本)是:
Function Count()
List<int> 1.753
int[] 2.304
函数计数()
清单1.753
int[]2.304
我完全无法解释这最后一个结果…+1很有意思。而且,你的数字似乎是64位的。在32位中,差异要大得多!也许这有助于我的测试速度实际上慢了四倍!最有意思的是。在32位中,我大约慢了38倍
O
对不起,这不是真的。int[]
确实实现了ICollection
我已经完成了Enumerable.Count()的实现,并验证了它没有执行两次强制转换。是的,int[]
确实实现了ICollection
但是强制转换本身很慢…将修改答案…@MatthewWatson:我想我们都得出了这样的结论,对ICollection
的强制转换在数组的情况下是特殊处理的。我们可以得出这样的结论:ICollection是在运行时通过执行一些疯狂的操作来检索的我的评论是按照这个答案的原始形式写的,它不真实
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
int dummy = 0;
int count = 1000000000;
var array = new int[1] as ICollection<int>;
var list = new List<int> {0};
var sw = Stopwatch.StartNew();
for (int i = 0; i < count; ++i)
dummy += array.Count;
Console.WriteLine("Array elapsed = " + sw.Elapsed);
dummy = 0;
sw.Restart();
for (int i = 0; i < count; ++i)
dummy += list.Count;
Console.WriteLine("List elapsed = " + sw.Elapsed);
Console.ReadKey(true);
}
}
}
Array elapsed = 00:00:02.9586515
List elapsed = 00:00:00.6098578
public static int Count<TSource>(IEnumerable<TSource> source)
{
var array = source as TSource[];
if (array != null) // Optimised for arrays.
return array.Length; // This is executed for int[]
ICollection<TSource> is2 = source as ICollection<TSource>;
if (is2 != null)
return is2.Count; // This is executed for List<int>.
ICollection is3 = source as ICollection;
if (is3 != null)
return is3.Count;
int num = 0;
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
num++;
}
return num;
}
Function Count()
List<int> 1.753
int[] 2.304