当Linq比Foreach更快时,情况确实如此

时间:2014-11-07 21:20:03

标签: c# linq foreach

如果你在Linq中搜索得更快,那么答案永远不会是一个foreach。我还发现了另一个stackoverflow问题问题asker had not done a "warmup"所以我已经包含了#34;热身"在我的代码中。

由于某些原因,我的代码示例没有按照我的预期行事。我正在思考我所做的是让无linq路径循环两次 - 一次是第一次,一次是总和。其中linq示例在它结束时仅在结尾处循环一次。你怎么看?我的测试是否存在缺陷,或者这是linq实际上为我们带来了良好的性能提升的情况?

    public class NumberObject { public Int32 Number { get; set; } }

    public IEnumerable<NumberObject> GetNumbersWithoutLambda()
    {
        IEnumerable<Int32> numberRange = Enumerable.Range(0,10);
        List<NumberObject> numberList = new List<NumberObject>();
        foreach (Int32 item in numberRange)
        {
            numberList.Add(new NumberObject() { Number = item });
        }
        return numberList;
    }

    public IEnumerable<NumberObject> GetNumbersWithLambda()
    {
        IEnumerable<Int32> numberRange = Enumerable.Range(0, 10);
        IEnumerable<NumberObject> numbers = numberRange.
            Select(number => new NumberObject() { Number = number });
        return numbers;
    }

    private void runGetNumbers(Func<IEnumerable<NumberObject>> getNumbersFunction, Int32 numberOfTimesToRun)
    {
        for (int i = 0; i < numberOfTimesToRun; i++)
        {
            IEnumerable<NumberObject> numbers = getNumbersFunction();
            //numbers.Count();
            numbers.Sum(item => item.Number);
            //numbers.Average(item => item.Number);
        }
    }

    [TestMethod]
    public void record_speed_of_GetNumbers()
    {
        Int32 numberOfTimes = 10000000;

        Console.WriteLine("Doing warmup... " +
            TimeMethod(() => runGetNumbers(GetNumbersWithLambda, numberOfTimes)));

        Console.WriteLine("GetNumbersWithoutLambda: " +
            TimeMethod(() => runGetNumbers(GetNumbersWithoutLambda, numberOfTimes)) + " milliseconds");

        Console.WriteLine("GetNumbersWithLambda: " +
            TimeMethod(() => runGetNumbers(GetNumbersWithLambda, numberOfTimes)) + " milliseconds");
    }

    static long TimeMethod(Action methodToTime)
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        methodToTime();
        stopwatch.Stop();
        return stopwatch.ElapsedMilliseconds;
    }

以下是测试的输出:

做热身...... 7560

GetNumbersWithoutLambda: 14779毫秒

GetNumbersWithLambda: 7626毫秒

有趣的是,#34;热身&#34;在这种情况下,实际上似乎并不适用。

4 个答案:

答案 0 :(得分:11)

当LINQ可以利用延迟执行时,它通常会更快;就像它在这里一样。

你怀疑; foreach完全枚举代码中的集合。 Select只是构建一个查询来执行该枚举。

然后当您调用Sum时,它会枚举先前生成的集合。我们foreach共有2个枚举,而Select只有一个枚举,因此它更快(按2倍!)

还有其他例子; TakeFirst将提前停止执行(foreach 可以提前停止,但大多数人都不会这样编码)。

基本上,当实际需要枚举整个集合以及您想要枚举(使用)LINQ查询时,请使用foreach。在运行查询和操作时使用LINQ,其中延迟执行将为您带来性能优势。当该查询返回集合时,使用foreach迭代 it

答案 1 :(得分:6)

您正在比较苹果和橘子,Linq不使用List&lt;&gt;就像你的“非lambda”版本一样。该清单不是免费的。

你需要这样写:

public IEnumerable<NumberObject> GetNumbersWithoutLambda() {
    IEnumerable<Int32> numberRange = Enumerable.Range(0, 10);
    foreach (Int32 item in numberRange) {
        yield return new NumberObject() { Number = item };
    }
}

现在需要相同的时间。是的,Linq也使用了迭代器。


然而,对于未经过验证的版本而言,它并没有结束,它的强度五倍

static int sum;   // Ensures summing doesn't get optimized away

private void runGetNumbers2(Int32 numberOfTimesToRun) {
    for (int i = 0; i < numberOfTimesToRun; i++) {
        foreach (var number in Enumerable.Range(0, 10)) {
            sum += number;
        }
    }
}

通过删除Enumerable.Range使其快三次次:

for (int i = 0; i < numberOfTimesToRun; i++) {
    for (int j = 0; j < 10; ++j) {
        sum += j;
    }
}

这表明迭代器使用的状态机也不是免费的。这里的基本前提是简单的代码很快。

答案 2 :(得分:5)

不同之处在于,即使List实现了IEnumerable,它也必须在方法返回之前完全填充,Linq方法只需要在返回之前构造表达式树。

考虑并计算以下时间:

public IEnumerable<NumberObject> GetNumbersWithLambdaToList()
{
    IEnumerable<Int32> numberRange = Enumerable.Range(0, 10);
    IEnumerable<NumberObject> numbers = numberRange.
        Select(number => new NumberObject() { Number = number });
    return numbers.ToList();
}

public IEnumerable<NumberObject> GetNumbersWithYield()
{
    IEnumerable<Int32> numberRange = Enumerable.Range(0,10);
    foreach (Int32 item in numberRange)
    {
        yield return (new NumberObject() { Number = item });
    }
}

在我的机器上:

GetNumbersWithoutLambda: 9631 milliseconds
GetNumbersWithLambda: 7285 milliseconds
GetNumbersWithLambdaToList: 12998 milliseconds
GetNumbersWithYield: 9236 milliseconds

答案 3 :(得分:0)

不完全是。这取决于你对Linq做了什么

如果您只是使用foreach来迭代和修改集合中的项目而不创建新集合。然后Linq一般比较慢

但是大多数人倾向于在漫长的逻辑中创建集合以进行迭代。分配内存并使其比使用Linq慢,因为Linq不创建真正的集合,它只记得在foreach

时执行的迭代逻辑

如果没有必要,过度使用Linq有时会减速。因为使用Linq创建委托对象并导致堆栈分配