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

标签 c# linq optimization enumerable

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

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 简单地迭代第一个序列。

我不明白的是:FooBFooA 慢 10 多倍,而 FooB 是实际上要简单得多(没有调用 Intersect() 方法)。

这是结果:

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

FooB 通过直接返回 source.First() 可以快很多,无论如何我使用 ILSpy 反编译了 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);  

最佳答案

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

如果你把它改成

    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 事件。实际上,您确实将苹果与橙子进行了比较。您的聚合调用基本上什么也没做。

关于c# - 为什么这个 IEnumerable 扩展方法比另一个(更简单的)扩展方法(只迭代输入)慢得多?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25591660/

相关文章:

java - 如何使用 Criterion 高效删除实体?

c# - 我如何充分了解 CLR 以对性能问题做出有根据的猜测?

c# - DbContext 的 EF 5.x 代码优先连接字符串不起作用

c# - ASP.NET MVC 中具有不同属性的部分 Controller

javascript - 将 C# 正则表达式转换为 JavaScript 正则表达式

c# - 将方法语法转换为其在 Linq 语法中的相应版本

c# - Linq 拼图 : How do I convert this foreach to linq syntax?

linq - Linq 中的“和”运算符

c# - 我应该使用什么 .NET 类来表示子网及其掩码?

c++ - gcc 的 asm volatile 是否等同于递归的 gfortran 默认设置?