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()