【问题标题】:Foreach MUCH faster than For in some cases?在某些情况下,Foreach 比 For 快得多?
【发布时间】:2017-10-29 06:40:25
【问题描述】:

我不喜欢过早的优化,但我在做一个简单的任务时很好奇 所以我加了一个秒表。 我不明白差异怎么会这么大。

每个 [richtextbox.text] 的字符串数组(7 个字符)。

数组长度:5500 个元素。

Foreach 已用时间:0.0015 秒

经过时间:9.757 秒

为:

if (chkLineBreaks.Checked)
{
    for (int i = 0; i < txtInput.Lines.Length; i++)
    {
        outputStringBuilder.Append($@"'{txtInput.Lines[i]}',");
    }
}

Foreach:

foreach (var line in txtInput.Lines)
{
    outputStringBuilder.Append($@"'{line}',");
    if (chkLineBreaks.Checked)
        outputStringBuilder.AppendLine();
}

根据我的阅读,差异应该可以忽略不计,而 For 会稍微快一些。

更重要的是,foreach 在每次迭代中都有一个条件(除非它在循环之前被“提升”。

这是怎么回事?

编辑: 我已将 foreach 代码更改为:

int i = 0;
foreach (var line in txtInput.Lines)
{
    outputStringBuilder.Append($@"'{txtInput.Lines[i]}',");
    i++;
}

所以它现在也在做同样的事情。 它需要 4.625 秒.. 仍然是 FOR 时间的一半左右

我也知道我可以在循环之外提取数组,但这不是我在这里测试的:)

编辑#2: 这是该部分的全部代码:

Stopwatch sw = new Stopwatch();
        sw.Start();
        //    for (int i = 0; i < txtInput.Lines.Length; i++)
        //    {
        //        outputStringBuilder.Append($@"'{txtInput.Lines[i]}',");
        //    }
        int i = 0;
        foreach (var line in txtInput.Lines)
        {
            outputStringBuilder.Append($@"'{txtInput.Lines[i]}',");
            i++;
        }
        MessageBox.Show(sw.Elapsed.ToString());

【问题讨论】:

  • 您可能还想尝试将txtInput.Lines.Length 保存到一个变量中,然后在 for 循环的条件部分中使用该变量。看看这对性能有何影响。
  • 9 秒来自与 for/foreach 无关的东西。可能是一些与 UI 线程之间的编组问题。另请注意,这两个测试是不等价的,因为在 foreach 情况下,您还要检查 chkLineBreaks.Checked。
  • 代码中的差异太多,无法得出任何真正的结论。如果您真的想要比较,请让两段代码完全相同,除了“for”和“foreach”。
  • 请检查编辑
  • 在内部,foreach 命令在给定值数组时执行for 循环。如果使用得当,结果应该是相同的。

标签: c# arrays for-loop foreach


【解决方案1】:

问题是txtInput.Lines 在您的for 循环中执行了多次(每行一次)(由于使用了txtInput.Lines[i])。 因此,对于文件的每一行,您都在说“好的,请将此文本框解析为多行 - 然后给我第 n 行” - 解析是杀手锏。

为了更公平的比较:

if (chkLineBreaks.Checked)
{
    var lines = txtInput.Lines;
    for (int i = 0; i < lines.Length; i++)
    {
        outputStringBuilder.Append($@"'{lines[i]}',");
    }
}

这样Lines 调用只执行一次(即相当于foreach 场景)。

发现此类问题的一种方法是比较时间。慢的比快的慢大约 6K,你有 5.5K 条目。由于 5.5K 和 6K 是非常相似的数字,它可能会促使您思考“我是否在循环中做了一些我真的不应该做的事情?”

【讨论】:

  • 我已将 foreach 更改为与 for 相同 - 请检查编辑。
  • @AngelicCore:这仍然不是一回事。 foreach 调用 txtInput.Lines.GetEnumerator(),它只计算一次 .Linesfor 必须一遍又一遍地评估 .Lines,除非您将循环更改为 mjwills 建议的内容。
  • 按照你的要求做了 - 现在 FOR 回到 0.001 秒
【解决方案2】:

在遍历数组(或列表)时,编译后的代码在 forforeach 语句之间几乎没有区别。

考虑一下这个简单的代码,它以三种不同的方式写出一个字符串列表:

class Program
{
    static void Main(string[] args)
    {
        var list = Enum.GetNames(typeof(System.UriComponents));

        // 1. for each
        foreach (var item in list)
        {
            Console.WriteLine(item);
        }
        // 2. for loop
        for (int i = 0; i<list.Length; i++)
        {
            Console.WriteLine(list[i]);
        }
        // 3. LINQ
        Console.WriteLine(string.Join(Environment.NewLine, list));
    }
}

现在查看MSIL 编译后的代码,使用ILSpyDotNetPeek 转换回C#

// ConsoleApplication1.Program
private static void Main(string[] args)
{
    string[] list = Enum.GetNames(typeof(UriComponents));
    string[] array = list;
    for (int j = 0; j < array.Length; j++)
    {
        string item = array[j];
        Console.WriteLine(item);
    }
    for (int i = 0; i < list.Length; i++)
    {
        Console.WriteLine(list[i]);
    }
    Console.WriteLine(string.Join(Environment.NewLine, list));
}

查看两个 for 循环。 foreach 语句被编译器变成了for 循环。至于string.Join() 语句,它调用SZArrayEnumerator,其中包含对数组的引用和当前索引值。在每次.MoveNext() 调用时,索引都会增加并返回一个新值。基本上相当于如下:

int i = 0;
while (i<list.Length)
{
    Console.WriteLine(list[i]);
    i++;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-12
    • 2016-06-23
    • 2016-11-18
    • 1970-01-01
    • 2022-01-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多