C# LINQ性能计数与位置和计数
测试:C# LINQ性能计数与位置和计数,c#,linq,C#,Linq,测试: public class Group { public string Name { get; set; } } List_groups=newlist(); 对于(int i=0;ix.Name==group.Name); } _秒表2.Stop(); 控制台写入线(_秒表2.ElapsedMilliseconds); 秒表_Stopwatch=新秒表(); _秒表。开始(); foreach(var组在_组中) { var count=_groups.Where(x=>x.
public class Group
{
public string Name { get; set; }
}
List_groups=newlist();
对于(int i=0;i<10000;i++)
{
var group=新组();
group.Name=i+“asdasdasd”;
_组。添加(组);
}
秒表_stopwatch2=新秒表();
_秒表2.Start();
foreach(var组在_组中)
{
var count=\u groups.count(x=>x.Name==group.Name);
}
_秒表2.Stop();
控制台写入线(_秒表2.ElapsedMilliseconds);
秒表_Stopwatch=新秒表();
_秒表。开始();
foreach(var组在_组中)
{
var count=_groups.Where(x=>x.Name==group.Name.count();
}
_秒表;
控制台写入线(秒表延时百万秒);
结果:第一名:2863,第二名:2185
有人能解释一下为什么第一种方法比第二种慢吗?第二个应该返回枚举数并对其调用count,第一个应该只调用count。第一种方法应该快一点
编辑:我删除了计数器列表以防止使用GC,并更改了顺序以检查排序是否有意义。结果几乎相同
EDIT2:此性能问题不仅与计数有关。它与First()、FirstOrDefault()、Any()等相关。。其中+方法总是比方法快。博尔希中士在评论中给出了正确的答案,但没有进一步解释 问题在于,字节码必须在第一次运行时由JIT编译器编译成x86。因此,您的度量包含了您想要测试的内容和编译时间。由于第二个测试使用的大部分内容都是在第一个测试期间编译的(列表枚举器、名称属性getter等),因此第一个测试受编译的影响更大
解决方案是做一个“热身”:您只运行一次代码,而不进行度量,通常只进行一次迭代,只是为了编译它。然后启动秒表并再次实际运行,需要多少次迭代才能获得足够长的持续时间(例如1秒)。在我看来,区别在于Linq扩展的编码方式。我怀疑
Where
正在使用列表中的优化来加速操作,但是Count
只是迭代一个IEnumerable
如果执行相同的过程,但使用IEnumerable
,则两种方法都很接近,其中的速度稍慢
List<Group> _groups = new List<Group>();
for (int i = 0; i < 10000; i++)
{
var group = new Group();
group.Name = i + "asdasdasd";
_groups.Add(group);
}
Stopwatch _stopwatch2 = new Stopwatch();
_stopwatch2.Start();
foreach (var group in _groups)
{
var count = _groups.Count(x => x.Name == group.Name);
}
_stopwatch2.Stop();
Console.WriteLine(_stopwatch2.ElapsedMilliseconds);
Stopwatch _stopwatch = new Stopwatch();
_stopwatch.Start();
foreach (var group in _groups)
{
var count = _groups.Where(x => x.Name == group.Name).Count();
}
_stopwatch.Stop();
Console.WriteLine(_stopwatch.ElapsedMilliseconds);
List_groups=newlist();
对于(int i=0;i<10000;i++)
{
var group=新组();
group.Name=i+“asdasdasd”;
_组。添加(组);
}
IEnumerable\u groupsEnumerable=从组中的g选择g;
秒表_stopwatch2=新秒表();
_秒表2.Start();
foreach(var组在_组中)
{
var count=\u groupsEnumerable.count(x=>x.Name==group.Name);
}
_秒表2.Stop();
控制台写入线(_秒表2.ElapsedMilliseconds);
秒表_Stopwatch=新秒表();
_秒表。开始();
foreach(var组在_组中)
{
var count=_groupsEnumerable.Where(x=>x.Name==group.Name.count();
}
_秒表;
控制台写入线(秒表延时百万秒);
其中扩展方法。注意if(源代码是List)
的情况:
List<Group> _groups = new List<Group>();
for (int i = 0; i < 10000; i++)
{
var group = new Group();
group.Name = i + "asdasdasd";
_groups.Add(group);
}
IEnumerable<Group> _groupsEnumerable = from g in _groups select g;
Stopwatch _stopwatch2 = new Stopwatch();
_stopwatch2.Start();
foreach (var group in _groups)
{
var count = _groupsEnumerable.Count(x => x.Name == group.Name);
}
_stopwatch2.Stop();
Console.WriteLine(_stopwatch2.ElapsedMilliseconds);
Stopwatch _stopwatch = new Stopwatch();
_stopwatch.Start();
foreach (var group in _groups)
{
var count = _groupsEnumerable.Where(x => x.Name == group.Name).Count();
}
_stopwatch.Stop();
Console.WriteLine(_stopwatch.ElapsedMilliseconds);
公共静态IEnumerable Where(此IEnumerable源,Func谓词)
{
if(source==null)
{
抛出错误。ArgumentNull(“源”);
}
if(谓词==null)
{
抛出错误。ArgumentNull(“谓词”);
}
if(源是可枚举的。迭代器)
{
返回((Enumerable.Iterator)源),其中(谓词);
}
如果(源为TSource[])
{
返回新的可枚举。whererrayiterator((TSource[])源,谓词);
}
如果(来源是列表)
{
返回新的Enumerable.WhereListIterator((列表)源,谓词);
}
返回新的可枚举。WhereEnumerableInterator(源,谓词);
}
计数法。只需遍历IEnumerable:
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
if (predicate == null)
{
throw Error.ArgumentNull("predicate");
}
if (source is Enumerable.Iterator<TSource>)
{
return ((Enumerable.Iterator<TSource>)source).Where(predicate);
}
if (source is TSource[])
{
return new Enumerable.WhereArrayIterator<TSource>((TSource[])source, predicate);
}
if (source is List<TSource>)
{
return new Enumerable.WhereListIterator<TSource>((List<TSource>)source, predicate);
}
return new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
}
public static int Count(此IEnumerable源,Func谓词)
{
if(source==null)
{
抛出错误。ArgumentNull(“源”);
}
if(谓词==null)
{
抛出错误。ArgumentNull(“谓词”);
}
int num=0;
选中的
{
foreach(t源中的源电流)
{
if(谓词(当前))
{
num++;
}
}
返回num;
}
}
我猜:
.Where()使用特殊的“WhereListIterator”来迭代元素,Count()则不使用,如Wyatt Earp所示。有趣的是迭代器被标记为“ngenable”:
[TargetedPatchingOptOut(“性能对于跨NGen映像边界内联此类方法至关重要”)]
公共WhereListIterator(列表源,Func谓词)
{
this.source=源;
this.predicate=谓词;
}
这可能意味着“迭代器”部分作为“非托管代码”运行,而Count()作为托管代码运行。我不知道这是否有意义/如何证明它,但那是我的0.2分
另外,如果您重写Count()以仔细处理列表
您可以使其保持不变/甚至更快:
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public WhereListIterator(List<TSource> source, Func<TSource, bool> predicate)
{
this.source = source;
this.predicate = predicate;
}
公共静态类TestExt{
公共静态int CountFaster(此IEnumerable源,Func谓词){
如果(source==null)抛出新异常();
if(谓词==null)抛出新异常();
如果(来源是列表)
{
int finalCount=0;
变量列表=(列表)源;
var count=list.count;
对于(var j=0;j
}
在我的测试中;在我开始使用CountFaster()之后,稍后被调用的人将获胜(因为冷启动)。关键在于W的实现
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public WhereListIterator(List<TSource> source, Func<TSource, bool> predicate)
{
this.source = source;
this.predicate = predicate;
}
public static class TestExt{
public static int CountFaster<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
if (source == null) throw new Exception();
if (predicate == null) throw new Exception();
if(source is List<TSource>)
{
int finalCount=0;
var list = (List<TSource>)source;
var count = list.Count;
for(var j = 0; j < count; j++){
if(predicate(list[j]))
finalCount++;
}
return finalCount;
}
return source.Count(predicate);
}
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
if (source is List<TSource>) return new WhereListIterator<TSource>((List<TSource>)source, predicate);
return new WhereEnumerableIterator<TSource>(source, predicate);
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Demo
{
public class Group
{
public string Name
{
get;
set;
}
}
internal static class Program
{
static void Main()
{
int dummy = 0;
List<Group> groups = new List<Group>();
for (int i = 0; i < 10000; i++)
{
var group = new Group();
group.Name = i + "asdasdasd";
groups.Add(group);
}
Stopwatch stopwatch = new Stopwatch();
for (int outer = 0; outer < 4; ++outer)
{
stopwatch.Restart();
foreach (var group in groups)
dummy += TestWhere(groups, x => x.Name == group.Name).Count();
Console.WriteLine("Using TestWhere(): " + stopwatch.ElapsedMilliseconds);
stopwatch.Restart();
foreach (var group in groups)
dummy += TestCount(groups, x => x.Name == group.Name);
Console.WriteLine("Using TestCount(): " + stopwatch.ElapsedMilliseconds);
stopwatch.Restart();
foreach (var group in groups)
dummy += TestListCount(groups, x => x.Name == group.Name);
Console.WriteLine("Using TestListCount(): " + stopwatch.ElapsedMilliseconds);
}
Console.WriteLine("Total = " + dummy);
}
public static int TestCount<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
int count = 0;
foreach (TSource element in source)
{
if (predicate(element))
count++;
}
return count;
}
public static int TestListCount<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
return testListCount((List<TSource>) source, predicate);
}
private static int testListCount<TSource>(List<TSource> source, Func<TSource, bool> predicate)
{
int count = 0;
foreach (TSource element in source)
{
if (predicate(element))
count++;
}
return count;
}
public static IEnumerable<TSource> TestWhere<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
return new WhereListIterator<TSource>((List<TSource>)source, predicate);
}
}
class WhereListIterator<TSource>: Iterator<TSource>
{
readonly Func<TSource, bool> predicate;
List<TSource>.Enumerator enumerator;
public WhereListIterator(List<TSource> source, Func<TSource, bool> predicate)
{
this.predicate = predicate;
this.enumerator = source.GetEnumerator();
}
public override bool MoveNext()
{
while (enumerator.MoveNext())
{
TSource item = enumerator.Current;
if (predicate(item))
{
current = item;
return true;
}
}
Dispose();
return false;
}
}
abstract class Iterator<TSource>: IEnumerable<TSource>, IEnumerator<TSource>
{
internal TSource current;
public TSource Current
{
get
{
return current;
}
}
public virtual void Dispose()
{
current = default(TSource);
}
public IEnumerator<TSource> GetEnumerator()
{
return this;
}
public abstract bool MoveNext();
object IEnumerator.Current
{
get
{
return Current;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
void IEnumerator.Reset()
{
throw new NotImplementedException();
}
}
}
TestCount():
.method public hidebysig static int32 TestCount<TSource>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource> source, class [mscorlib]System.Func`2<!!TSource, bool> predicate) cil managed
{
.maxstack 8
.locals init (
[0] int32 count,
[1] !!TSource element,
[2] class [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource> CS$5$0000)
L_0000: ldc.i4.0
L_0001: stloc.0
L_0002: ldarg.0
L_0003: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource>::GetEnumerator()
L_0008: stloc.2
L_0009: br L_0025
L_000e: ldloc.2
L_000f: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource>::get_Current()
L_0014: stloc.1
L_0015: ldarg.1
L_0016: ldloc.1
L_0017: callvirt instance !1 [mscorlib]System.Func`2<!!TSource, bool>::Invoke(!0)
L_001c: brfalse L_0025
L_0021: ldloc.0
L_0022: ldc.i4.1
L_0023: add.ovf
L_0024: stloc.0
L_0025: ldloc.2
L_0026: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
L_002b: brtrue.s L_000e
L_002d: leave L_003f
L_0032: ldloc.2
L_0033: brfalse L_003e
L_0038: ldloc.2
L_0039: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_003e: endfinally
L_003f: ldloc.0
L_0040: ret
.try L_0009 to L_0032 finally handler L_0032 to L_003f
}
testListCount():
.method private hidebysig static int32 testListCount<TSource>(class [mscorlib]System.Collections.Generic.List`1<!!TSource> source, class [mscorlib]System.Func`2<!!TSource, bool> predicate) cil managed
{
.maxstack 8
.locals init (
[0] int32 count,
[1] !!TSource element,
[2] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!!TSource> CS$5$0000)
L_0000: ldc.i4.0
L_0001: stloc.0
L_0002: ldarg.0
L_0003: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> [mscorlib]System.Collections.Generic.List`1<!!TSource>::GetEnumerator()
L_0008: stloc.2
L_0009: br L_0026
L_000e: ldloca.s CS$5$0000
L_0010: call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator<!!TSource>::get_Current()
L_0015: stloc.1
L_0016: ldarg.1
L_0017: ldloc.1
L_0018: callvirt instance !1 [mscorlib]System.Func`2<!!TSource, bool>::Invoke(!0)
L_001d: brfalse L_0026
L_0022: ldloc.0
L_0023: ldc.i4.1
L_0024: add.ovf
L_0025: stloc.0
L_0026: ldloca.s CS$5$0000
L_0028: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator<!!TSource>::MoveNext()
L_002d: brtrue.s L_000e
L_002f: leave L_0042
L_0034: ldloca.s CS$5$0000
L_0036: constrained [mscorlib]System.Collections.Generic.List`1/Enumerator<!!TSource>
L_003c: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0041: endfinally
L_0042: ldloc.0
L_0043: ret
.try L_0009 to L_0034 finally handler L_0034 to L_0042
}
callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource>::get_Current()
callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator<!!TSource>::get_Current()
call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator<!!TSource>::MoveNext()