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

Sim*_*Cat 1 c# linq foreach

如果你的搜索是Linq更快的Foreach答案总是不是一个foreach是.我还发现了另一个stackoverflow问题,问题提问者没有做过"热身",所以我在我的代码中加入了"热身".

我的代码示例由于某种原因没有像我预期的那样行事.我在想我做的是让无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;
    }
Run Code Online (Sandbox Code Playgroud)

以下是测试的输出:

做热身...... 7560

GetNumbersWithoutLambda: 14779毫秒

GetNumbersWithLambda: 7626毫秒

有趣的是,"热身"运行实际上似乎并不适用于这种情况.

Bra*_*NET 11

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

如你所料; foreach在代码中完全枚举集合.Select只是构建一个查询来执行该枚举.

然后,当您调用时Sum,它枚举以前生成的集合.我们总共有2个枚举foreach,只有一个Select,所以速度更快(2倍!)

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

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


Han*_*ant 6

你正在比较苹果和橘子,Linq没有像你的"非lambda"版本那样使用List <>.那份清单不是免费的.

你需要像这样写:

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

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


然而,这并不望其项背的unlinquified版本,它是5快倍:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

让它另一个3更快倍还通过降低Enumerable.Range:

for (int i = 0; i < numberOfTimesToRun; i++) {
    for (int j = 0; j < 10; ++j) {
        sum += j;
    }
}
Run Code Online (Sandbox Code Playgroud)

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


psa*_*ton 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 });
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的机器上:

GetNumbersWithoutLambda: 9631 milliseconds
GetNumbersWithLambda: 7285 milliseconds
GetNumbersWithLambdaToList: 12998 milliseconds
GetNumbersWithYield: 9236 milliseconds
Run Code Online (Sandbox Code Playgroud)