【问题标题】:Difference in performance c# foreach for linq [closed]linq的性能差异c#foreach[关闭]
【发布时间】:2011-08-08 20:51:28
【问题描述】:

我想知道这两者的区别:

为:

string[] arrayOld = new string(){"This", "other", "aaa", ...};
string[] arrayNew = new string[arrayOld.lenght];

for(int i = i < arrayOld.lenght; i++){
   arrayNew[i] = arrayOld[i];
}

FOREACH:

string[] arrayOld = new string(){"This", "other", "aaa", ...};
List<string> listNew = new List<string>();

foreach(string val in arrayOld){
   listNew.add(val);
}
string[] arrayNew = listNew.toArray();

LINQ:

string[] arrayOld = new string(){"This", "other", "aaa", ...};
string[] arrayNew = (from val in arrayOld select val).toArray();

我不想复制数组...

想法是从arrayOld中的对象构造新对象(可以不同于字符串,可以包含其他属性...)

我需要表现,所以...

¿什么是最好的选择,为什么?

【问题讨论】:

  • 你以前用过StopWatch吗?
  • 这段代码:(from val in arrayOld select val).toArray(); 是一个很好的例子,说明人们为什么应该熟悉非查询样式的 LINQ。也就是说:arrayOld.ToArray();
  • @ChaosPandion - 在 OPs 防御中,他的代码被正确缩进。 StackOverflow 中有一个错误,它不会格式化编号/项目符号列表中的代码。
  • 为什么这个问题被否决并投票结束?这家伙在问一个有效的问题。
  • @Robaticus:我可以想到几个原因:1)问题要求最好,但没有说哪种最好。没有明确的目的没有最好的。 2) 最初,问题的格式不正确且未指定。 3) 现在它有几个示例与问题不匹配的代码(请阅读最后一段。) 4) 我不喜欢这样的问题:“你能编译并运行它吗?让我看看哪个最好?” (这是公认的答案所做的)。 SO 不是每个人的个人人工编译器。

标签: c# .net linq performance foreach


【解决方案1】:

粗略的结果最能说明问题:

class Program
{
    static void Main()
    {
        // Warm-up
        Method1();
        Method2();
        Method3();

        const int Count = 1000000;

        var watch = Stopwatch.StartNew();
        for (int i = 0; i < Count; i++)
        {
            Method1();
        }
        watch.Stop();
        Console.WriteLine("Method1: {0} ms", watch.ElapsedMilliseconds);

        watch = Stopwatch.StartNew();
        for (int i = 0; i < Count; i++)
        {
            Method2();
        }
        watch.Stop();
        Console.WriteLine("Method2: {0} ms", watch.ElapsedMilliseconds);

        watch = Stopwatch.StartNew();
        for (int i = 0; i < Count; i++)
        {
            Method3();
        }
        watch.Stop();
        Console.WriteLine("Method3: {0} ms", watch.ElapsedMilliseconds);

    }

    static void Method1()
    {
        string[] arrayOld = new[] { "This", "other", "aaa" };
        string[] arrayNew = new string[arrayOld.Length];

        for (var i = 0; i < arrayOld.Length; i++)
        {
            arrayNew[i] = arrayOld[i];
        }
    }

    static void Method2()
    {
        string[] arrayOld = new[] { "This", "other", "aaa" }; 
        var listNew = new List<string>(arrayOld.Length);

        foreach(var val in arrayOld)
        { 
            listNew.Add(val); 
        } 
        string[] arrayNew = listNew.ToArray();    
    }

    static void Method3()
    {
        string[] arrayOld = new[] { "This", "other", "aaa" }; 
        string[] arrayNew = (from val in arrayOld select val).ToArray();    
    }
}

在我的机器上打印:

Method1: 72 ms
Method2: 187 ms
Method3: 377 ms

【讨论】:

  • 但同样,在情况 2 中,您正在为列表重新分配计时。将案例 1 更改为也使用列表。
  • @Blindy,我已经更新了 Method2,因此没有列表重新分配。
  • 我发现 Method2 更快,因为 List&lt;T&gt; 的默认大小是 4,这让我感到惊讶。我们可能会看到 .NET 运行时的结果有点摇摆不定。
  • 我想让他按照自己的方式工作,而不是把答案交给他:(
【解决方案2】:

以上都不是。

如果你想复制一个数组,我会尝试:

string[] arrayOld = new { "This", "other", "aaa" };
string[] arrayNew = new string[arrayOld.Length];

arrayOld.CopyTo(arrayNew, 0);

【讨论】:

  • +1。请注意,一般而言,为特定任务设计的专用方法将与此类方法的手动实现版本一样快或更快(因为人们通常会确保智能地实现此类方法)。因此,即使没有运行任何基准测试,我预测此方法在速度上与其他列出的方法相比具有竞争力。而且它是可读的,所以坚持使用它,除非您运行分析器并发现它是一个瓶颈。当然,在这种情况下,我会进一步预测您的算法存在算法缺陷,而不是需要更有效的数组复制例程。
【解决方案3】:

在大多数情况下,您应该为您正在编写的特定代码选择最清楚地表达意图的任何内容。但是,如果它是一个非常深的内部循环,并且您需要每隔最后一纳秒发出一声尖叫,我的经验是(使用 Release 构建代码)对数组进行索引的 for 循环比 foreach 循环快得多,而且稍微小一点使用带有 LINQ 的委托与带有循环内部逻辑的简单 foreach 的性能损失。

这些观察结果基于人工智能中使用的分数计算算法的微优化工作,其中分数函数被多次评估。具体的瓶颈和改进程度是使用分析工具确定的,如果您处于这种情况,我强烈建议您这样做。瓶颈很少出现在您认为的位置。

【讨论】:

    【解决方案4】:

    至于性能for 可能会超过其他性能,因为您直接使用索引器,而在foreach 中您使用的是Enumerator,因此您有更多行要执行(GetEnumerator、MoveNext、Current 等) .. 但区别是非常微妙,并为代码的可读性可维护性付出了代价。

    至于 LINQ 的工作量更大。但是想想为什么 LINQ 应该更快?它还在内部使用循环。

    大多数时候,LINQ 会有点慢,因为它会引入开销。如果您非常关心性能,请不要使用 LINQ。使用 LINQ 是因为您想要更短、更易读、更易于维护的代码。


    也就是说,如果您对性能过于敏感,并且希望在最后一个时钟周期中输出,那么您可能希望在 unsafe 上下文中使用 C 风格的 pointers fixed变量

    【讨论】:

    • @Blindy - 嗯......这完全是错误的。 foreach 循环必须经历获取枚举器和枚举集合的麻烦。 for 循环很简单。如果 for 循环对你有用……它会更快。
    • @贾斯汀·尼斯纳 |那不是我说的.. for 很简单,foreach 使用 Enumerator,你能指出错误吗
    • @Shekhar,我认为贾斯汀的评论是为了获得不同的答案(现已删除)。
    • @Anthony Pegram |哦,好吧...没有问题:)
    • 我没有观察到使用不安全的指针取消引用与索引器取消引用相比有明显的改进。如果您查看抖动为数组索引生成的机器代码,您会发现它无论如何都在使用直接相对地址取消引用。
    【解决方案5】:

    最快的方法是Array.Copy

    string[] arrayOld = new string[] { "This", "other", "aaa" };
    string[] arrayNew = new string[arrayOld.Length];
    
    Array.Copy(arrayOld, arrayNew, arrayOld.Length);
    

    【讨论】:

    • Array.CopyTo 对我来说总是更清晰(而且我在 6 秒内找到了你……呵呵)。
    【解决方案6】:

    Daniel A. White 的评论,不确定是否严重,但他是对的。您听说过 StopWatch 类吗?

    我认为现在是您花点时间学习如何为自己判断表现的时候了。如果我们中的一个人真的想正确回答您的问题,我们将不得不编写性能检查代码,这就是为什么您的问题可能会收到如此多的反对票。因为你应该这样做。但我们会帮助你:)

    您可以使用 StopWatch 类以及许多其他技术(计数时钟周期/迭代)以确定哪种方法具有最佳性能。

    http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx

    using System;
    using System.Diagnostics;
    using System.Threading;
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            Thread.Sleep(10000); // put one of your three scenarios here
            stopWatch.Stop();
            // Get the elapsed time as a TimeSpan value.
            TimeSpan ts = stopWatch.Elapsed;
    
            // Format and display the TimeSpan value.
            string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
                ts.Hours, ts.Minutes, ts.Seconds,
                ts.Milliseconds / 10);
            Console.WriteLine("RunTime " + elapsedTime);
        }
    }
    

    (来自 msdn 的代码 sn-p)

    【讨论】:

    • 这是真的,但我问,因为可以有更好的方法来制作......没有更多
    • 是的,我也会很好奇,不用担心。有些人真的不脚踏实地。
    猜你喜欢
    • 2012-01-03
    • 2014-12-15
    • 2013-02-18
    • 1970-01-01
    • 2010-11-05
    • 2010-11-10
    • 2016-05-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多