[英]Is the Linq Count() faster or slower than List.Count or Array.Length?
[英]Array.Count() much slower than List.Count()
當使用IEnumerable<T>
Count()
的擴展方法時,數組至少比列表慢兩倍。
Function Count()
List<int> 2,299
int[] 6,903
差異來自哪里?
我知道兩者都在調用ICollection
的Count
屬性:
如果源的類型實現ICollection,則該實現用於獲取元素的數量。 否則,此方法確定計數。
對於列表,它返回List<T>.Count
,對於array, Array.Length
。 而且, Array.Length
應該比List<T>.Count
。
基准測試:
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;
}
}
編輯:
leppie和Knaģis的答案非常驚人,但我想補充一句話。
正如Jon Skeet所說:
實際上有兩個等效的塊,只是測試不同的集合接口類型,並使用它首先找到的任何一個(如果有的話)。 我不知道.NET實現是否首先測試ICollection或ICollection <T> - 我可以通過實現兩個接口來測試它,但當然可以從每個接口返回不同的計數,但這可能是過度的。 除了輕微的性能差異之外,對於性能良好的集合並不重要 - 我們希望首先測試“最可能”的接口,我認為這是通用接口。
通用的可能是最有可能發生的,但如果你反轉這兩個,即在通用的之前調用非泛型強制轉換,Array.Count()變得比List.Count()快一點。 另一方面,List的非通用版本較慢。
很高興知道是否有人想在1e8迭代循環中調用Count()
!
Function ICollection<T> Cast ICollection Cast
List 1,268 1,738
Array 5,925 1,683
原因是Enumerable.Count<T>()
執行ICollection<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
關鍵可能是文檔中的這個陳述(強調我的):
在.NET Framework 2.0版中,Array類實現System.Collections.Generic.IList,System.Collections.Generic.ICollection和System.Collections.Generic.IEnumerable通用接口。 這些實現在運行時提供給數組 ,因此文檔構建工具不可見。 因此,通用接口不會出現在Array類的聲明語法中,並且沒有可通過將數組轉換為通用接口類型(顯式接口實現)來訪問的接口成員的參考主題。
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 build):
regsvr32 IronScheme.Profiler.x86.dll
set COR_PROFILER={9E2B38F2-7355-4C61-A54F-434B7AC266C0}
set COR_ENABLE_PROFILING=1
ConsoleApp1.exe
當應用程序退出時,會創建一個report.tab
文件,然后可以在Excel中使用該文件。
我發布這個,不是作為答案,而是提供一個更可測試的環境。
我已經獲取了Enumerable<T>.Count()
的實際實現的副本,並更改了原始測試程序以使用它,因此人們可以在調試器中單步執行它。
如果您運行以下代碼的發布版本,您將獲得與OP類似的時間。
對於List<T>
和int[]
,分配給is2
的第一個is2
將為非null,因此將調用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
我完全無法解釋這最后的結果......
這是因為int[]
需要強制轉換,而List<int>
則不需要。 如果你要使用Length屬性,那么結果將會大不相同 - 大約。 比List<int>.Count()
快10倍。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.