代码之家  ›  专栏  ›  技术社区  ›  tigrou

为什么这个IEnumerable扩展方法比另一个(更简单的)扩展方法(只迭代输入)慢得多?

  •  3
  • tigrou  · 技术社区  · 10 年前

    我有一个控制台应用程序,它包含两种方法:

    public static IEnumerable<TSource>
              FooA<TSource>(this IEnumerable<IEnumerable<TSource>> source)
    {
        return source.Aggregate((x, y) => x.Intersect(y));
    }
    
    public static IEnumerable<TSource> 
              FooB<TSource>(this IEnumerable<IEnumerable<TSource>> source)
    {
        foreach (TSource element in source.First())
        {
            yield return element;
        }
    }
    

    它的作用:两者都采用一系列序列, FooA 生成它们的交集集,然后返回结果。 FooB 只需重复第一个序列。

    我不明白的是: 食品B 食品安全局 虽然 食品B 实际上要简单得多(没有调用 Intersect() 方法)。

    以下是结果:

    00:00:00.0071053 (FooA)
    00:00:00.0875303 (FooB)
    

    食品B 直接返回可以更快 source.First() 无论如何,我反编译了 Distinct 方法,并找到完全相同的foreach yield返回循环:

    private static IEnumerable<TSource> DistinctIterator<TSource>
       (IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
    {
        Set<TSource> set = new Set<TSource>(comparer);
        foreach (TSource current in source)
        {
            if (set.Add(current))
            {
                yield return current;
            }
        }
        yield break;
    } 
    

    另外:在我使用的代码中,我无法返回 source.First() (我明白了 CS1622 ). 我在这里展示的实际上是一个更简单的代码,它是我为调试而精简的。

    下面是我用于测试的代码:

    List<List<int>> foo = new List<List<int>>();
    foo.Add(new List<int>(Enumerable.Range(0, 3000*1000)));
    
    Stopwatch sa = new Stopwatch();
    sa.Start();
    List<int> la = FooA(foo).ToList();
    Console.WriteLine(sa.Elapsed);
    
    
    Stopwatch sb = new Stopwatch();
    sb.Start();
    List<int> lb = FooB(foo).ToList();
    Console.WriteLine(sb.Elapsed);  
    
    3 回复  |  直到 10 年前
        1
  •  5
  •   Alois Kraus    10 年前

    之所以要测量如此大的差异,是因为Aggregate调用只返回初始列表,因为没有要聚合的项,因为列表只有一个项。

    如果您将其更改为

        List<List<int>> foo = new List<List<int>>()
        {
            new List<int>(Enumerable.Range(0, 3000 * 1000)),
            new List<int>(Enumerable.Range(0, 3000 * 1000)),
        };
    

    只有一个像你这样的项目:

    A: 00:00:00.0037843
    B: 00:00:00.0514177
    

    但有两项:

    A: 00:00:00.2130628
    B: 00:00:00.0574932
    

    A现在慢得多。第一个示例中的差异是由于阵列分配导致了更多的CPU周期。

        AllocationAmount AllocationKind
    B     1CAE0         Small
    B     21E5C         Small
    B     20020         Large
    B     40020         Large
    B     80020         Large
    B    100020         Large
    B    200020         Large
    B    400020         Large
    B    800020         Large
    B   1000020         Large
    A    B71B20         Large
    

    这是垃圾收集器发出的GC AllocationTick ETW事件。事实上,你确实把苹果和橘子作了比较。您的聚合调用基本上没有执行任何操作。

        2
  •  0
  •   user2370523 user2370523    10 年前

    请改用:

    public static IEnumerable<TSource> FooB<TSource>(this IEnumerable<IEnumerable<TSource>> source) {
        yield return source.First();
    }
    
        3
  •  0
  •   usr    10 年前

    FooA 不打电话 Intersect 完全序列中只有一个元素。 Aggregate 只是返回它。没有什么可聚合的。

    FooB 遍历第一序列的所有元素。这需要时间。这比仅仅返回第一个序列需要更长的时间 食品安全局