迭代Linq结果时出现奇怪的缓慢

时间:2009-10-17 13:27:56

标签: c# linq

在探索最近的Linq question时,我注意到算法似乎很慢。深入挖掘我注意到它不是linq代码,但结果的输出后来耗费了很长时间。 (感谢Marc Gravel顺便说一下,我已经看到过最狡猾的Linq了。)

代码:

        DateTime dt = DateTime.Now;
        Console.WriteLine("Alg Start " + dt.Second + "." + dt.Millisecond);

        var qry = from l in Enumerable.Range(100000, 999999)
                  let s = l.ToString()
                  let sReversed = new string(s.Reverse().ToArray())
                  from i in Enumerable.Range(3, 9)
                  let t = (l * i).ToString()
                  where t == sReversed
                  select new { l, i };

        dt = DateTime.Now;
        Console.WriteLine("Alg End " + dt.Second + "." + dt.Millisecond);

        foreach (var row in qry)
            Console.WriteLine("{0} x {1} = {2}", row.l, row.i, row.l * row.i);

        dt = DateTime.Now;
        Console.WriteLine("Disp End " + dt.Second + "." + dt.Millisecond);

输出:

Alg Start 20.257
Alg End   20.270
109989 x 9 = 989901
219978 x 4 = 879912
1099989 x 9 = 9899901
Disp End  31.322

.13秒计算和超过11来显示?!?这是什么原因?

6 个答案:

答案 0 :(得分:5)

linq查询似乎快速执行的原因是因为linq使用延迟执行,所以在定义点上实际上没有计算任何内容,即在您开始枚举结果之前不会执行“真正的”工作。

答案 1 :(得分:5)

原因是在枚举之前查询实际上并未运行。在LINQ to对象中,它只是设置了一堆代理,当你遍历枚举器时会被调用。如果您要在查询中添加ToList()以实现它,您会看到所花费的时间将转移到设置并远离显示。

答案 2 :(得分:3)

有许多linq提供程序,“alg start”到“alt end”只是被解析 - 在你真正开始枚举结果之前,不会评估实际表达式。因此,“qry”变量的实际创建速度很快(只需设置一个实际执行查询中逻辑的枚举),但通过它进行枚举的速度较慢。

答案 3 :(得分:3)

LINQ代码只从查询表达式中创建一个查询对象,这不会花费很多时间。只有在foreach中才是实际执行的查询。

顺便说一下,你不应该使用DateTime.Now来提高性能,而应该使用Stopwatch类,因为它更精确。

答案 4 :(得分:3)

在迭代之前,查询实际上不会计算。在此之前它就像一个SQL语句,等待执行。

答案 5 :(得分:2)

这个问题正在做蛮力;在这种情况下,LINQ实际上非常方便 - 我在这里讨论过:Brute force (but lazily)

只是为了扩展以前的一些答案:

LINQ通常是围绕延迟执行设计的,这意味着在开始迭代结果之前不会发生任何事情。这通常通过迭代器块完成;考虑这些之间的区别:

static IEnumerable<T> Where(this IEnumerable<T> data, Func<T,bool> predicate) {
    foreach(T item in data) {
        if(predicate(item)) yield return item;
    }
}

static IEnumerable<T> Where(this IEnumerable<T> data, Func<T,bool> predicate) {
    var list = new List<T>();
    foreach(T item in data) {
        if(predicate(item)) list.Add(item);
    }
    return list;
}

不同之处在于,第二个版本在您调用Where时返回单个结果时执行所有工作,其中 - 作为第二个(通过迭代器块的魔力)仅执行在枚举器调用MoveNext()时工作。迭代器块在C# in Depth免费样本第6章中进行了更多讨论。

通常,这样做的好处是它使查询可组合 - 对于基于数据库的查询尤其重要,但对于常规工作同样有效。

请注意,即使使用迭代器块,也需要第二次考虑;缓冲。考虑Reverse() - 无论如何操作,要反转序列,首先需要找到序列的结尾。现在考虑并非所有序列都结束!将其与WhereSkipTake等对比 - 可以在没有缓冲的情况下过滤行(只需删除项目)。

在无限序列中使用它的一个很好的例子是this Fibonacci question,我们可以使用非缓冲的延迟方法:

    foreach (long i in Fibonacci().Take(10)) {
        Console.WriteLine(i);
    }

如果没有延迟执行,这将永远不会完成。