使用IEnumerable<T>
Count()
的扩展方法时,数组的速度至少比列表慢两倍。
Function Count()
List<int> 2,299
int[] 6,903
差异来自哪里?
我知道两者都在调用Count
的{{1}}属性:
如果源的类型实现ICollection,则该实现用于获取元素的数量。否则,此方法确定计数。
对于列表,它返回List<T>.Count
,对于数组,ICollection
。此外,Array.Length
应该比Array.Length
更快。
基准:
List<T>.Count
编辑:
leppie和Knaģis的答案非常惊人,但我想补充一句话 As Jon Skeet said:
实际上有两个等效的块,只是测试 不同的集合接口类型,并使用它找到的任何一种 首先(如果有的话)。我不知道.NET实现是否测试 ICollection或ICollection&lt; T>首先 - 我可以通过实施来测试它 两个接口,但每个接收不同的计数,当然, 但这可能是矫枉过正的。这并不重要 除了轻微的性能差异之外,表现良好的收藏品 - 我们首先要测试“最有可能”的界面,我认为这是通用界面。
泛型可能是最有可能发生的,但是如果你反转这两个,即在泛型之前调用非泛型强制转换,则Array.Count()变得比List.Count()快一点。另一方面,List的非通用版本较慢。
很高兴知道是否有人想在1e8迭代循环中调用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("List<int>", Benchmark(list, Iterations));
results.Add("int[]", Benchmark(array, 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++) source.Count();
countWatch.Stop();
return countWatch.Elapsed;
}
}
!
Count()
答案 0 :(得分:9)
原因是Enumerable.Count<T>()
执行转换为ICollection<T>
以从列表和数组中检索计数。
使用此示例代码:
public static int Count<TSource>(IEnumerable<TSource> source)
{
ICollection<TSource> collection = source as ICollection<TSource>;
if (collection != null)
{
return 1; // collection.Count;
}
}
你可以确定演员阵容需要花费更长的时间,实际上大部分时间用于计算:
Function Count()
List<int> 1,575
int[] 5,069
关键可能是来自documentation(强调我的)的声明:
在.NET Framework 2.0版中,Array类实现System.Collections.Generic.IList, System.Collections.Generic.ICollection,和 System.Collections.Generic.IEnumerable通用接口。的的 实现在运行时提供给数组,因此是 文档构建工具不可见。结果,通用 接口不会出现在Array的声明语法中 类,并没有接口成员的参考主题 只能通过将数组转换为通用接口类型来访问 (显式接口实现)。
答案 1 :(得分:5)
32位分析分析(全部以ms为单位,仅有趣的位,JIT内联禁用):
Name Count 'Inc Time' 'Ex Time' 'Avg Inc Time' 'Avg Ex Time'
System.Linq.Enumerable::Count(<UNKNOWN>):int32 <System.Int32>
20000000 13338.38 7830.49 0.0007 0.0004
System.SZArrayHelper::get_Count():int32 <System.Int32>
10000000 4063.9 2651.44 0.0004 0.0003
System.Collections.Generic.List<System.Int32>::get_Count():int32
10000000 1443.99 1443.99 0.0001 0.0001
System.Runtime.CompilerServices.JitHelpers::UnsafeCast(Object):System.__Canon <System.__Canon>
10000004 1412.46 1412.46 0.0001 0.0001
System.SZArrayHelper::get_Count()
似乎为数组案例调用了System.Runtime.CompilerServices.JitHelpers::UnsafeCast
。
对于列表,List<int>.Count
只返回大小。
Inc time
费用包括儿童电话费。
Ex time
仅为方法主体的费用。
禁用内联时,Array.Count()
慢两倍。
这可能是因为提到了现在已删除的答案。看来应用的属性(ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)
和SecuritySafeCritical
)会阻止运行时内联调用,因此差异很大(在32位模式下,我的情况要慢38倍)。
自己介绍一下:
获取https://github.com/leppie/IronScheme/raw/master/IronScheme/tools/IronScheme.Profiler.x86.dll 运行应用程序(仅限x86版本):
regsvr32 IronScheme.Profiler.x86.dll
set COR_PROFILER={9E2B38F2-7355-4C61-A54F-434B7AC266C0}
set COR_ENABLE_PROFILING=1
ConsoleApp1.exe
当应用退出时,会创建一个report.tab
文件,然后可以在Excel中使用。
答案 2 :(得分:3)
我发布这个,不是作为答案,而是提供一个更可测试的环境。
我已经获取了Enumerable<T>.Count()
的实际实现的副本并更改了原始测试程序以使用它,因此人们可以在调试器中单步执行它。
如果您运行以下代码的发布版本,您将获得与OP类似的时间。
对于List<T>
和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;
}
}
}
根据这些信息,我们可以简化测试,只关注List.Count
和Array.Count
之间的时间差异:
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
此时,我自己“当然可以优化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;
}
值得注意的是,即使进行了此更改,我的系统上的数组仍然更慢,尽管非数组必须进行额外的转换!
我的结果(发布版本)是:
Function Count()
List<int> 1.753
int[] 2.304
我完全无法解释最后的结果......
答案 3 :(得分:1)
这是因为int[]
需要投射,而List<int>
则不需要投射。如果你要使用Length属性,那么结果将会大不相同 - 大约。比List<int>.Count()
快10倍。