【问题标题】:Efficiency of initializing strings inside or outside of a loop在循环内部或外部初始化字符串的效率
【发布时间】:2014-05-01 21:33:23
【问题描述】:

在 C# 控制台应用程序中,我有一个循环遍历集合中的大约 8000 个项目,设置大约八个字符串等于项目的各种属性,然后使用这些字符串执行各种其他操作。为了使我的代码井井有条,我提前声明了所有字符串并将它们保留为空,直到读取一个项目并且循环将适当的字符串设置为适当的值。我的问题是,在循环外部和内部声明这些字符串之间会有明显的性能差异吗?我知道它会在技术上消耗一些额外的时钟周期,每次循环迭代时声明它们,并且在 8000 次迭代之后可能会开始加起来,但我不知道有多少或者它是否重要?这个应用程序已经花费了一个半小时来完成它的整个周期,所以几秒钟的差异是微不足道的,但如果我们说的是几分钟,那显然是一件坏事。

请记住,这主要是一个好奇的问题,我可以诚实地在任何地方声明这些变量而不会影响应用程序。我确信最佳实践表明它们应该始终处于循环之外,但我一直想知道它在这种低影响操作中真正产生了多大的影响。

【问题讨论】:

  • 对其进行分析并查看。如果设置这两个选项并不难,然后运行它,看看你是否注意到差异
  • “我知道它会在技术上消耗一些额外的时钟周期,每次循环迭代时声明它们”——你怎么知道的? “我确信最佳实践表明它们应该始终在循环之外 [...]”——我很确定最好的做法是使它们的范围尽可能小,即,将它们声明为 inside 循环。
  • @delnan 你是对的,实际上是 编译器 的工作来执行将声明移出循环的优化。优化编译器有时可以并且确实会执行精确的优化,但我已经看到了它无法执行此操作的情况,并且自己执行此操作会对性能产生重大影响。但是当然optimization should only be done when necessary 然后只有在分析确定它会产生显着影响之后。

标签: c# string performance memory-management


【解决方案1】:

首先,8000 对于现代计算机来说是一个相对较小的数字,所以我不会担心诸如声明变量或使用属性之类的小事情,因为差异可以忽略不计。我认识一位同事,当我们优化我们的代码以进行需要我们几个操作的操作时,他坚持不使用属性。我一直告诉他,他在微不足道的事情上浪费了我们的时间,但他成功地说服了团队,他们在没有我的情况下进行了测试。循环有 100 万次迭代,但相差不到半秒。

现在就您而言,我相信实际上根本没有区别。再说一次,8000 太小了,甚至连考虑差异(如果存在)都懒得考虑。

至于最佳实践,实际上是相反的。范围应该尽可能小,所以在循环中声明它们。

这里有一个很好的test done by one of the SO users,有完整的讨论。

【讨论】:

  • 这说明了问题,谢谢!一百万次迭代后半秒?我认为我的情况的变化将是微不足道的,但这比我想象的影响要小得多。
  • @thanby 是的,我的目标是为您提供现实生活中的示例,以“透视”。我也很惊讶差异如此之小。我很确定它可以忽略不计,但预计至少会是几秒钟。
  • @thanby 我刚刚发现了一个很好的[由一位 SO 用户完成的测试][1]。我想分享它,因为它有补充我的答案的有用讨论。
【解决方案2】:

不会有任何区别(好吧,不应该)。至少,你甚至不应该去想它——编译器优化会为你做这件事。

基本上,当您为变量分配新的字符串值时,会发生以下情况:

  • 在堆中创建的新字符串(除非它被实习 - 为简单起见我们跳过它)
  • 复制的新字符串引用代替旧字符串引用(堆栈中的引用变量)
  • 旧字符串现在没有人引用,所以稍后会被垃圾收集器收集

真的在循环内或循环外声明变量并不重要 - 编译器(很可能)只会重用相同的堆栈区域以在两种情况下保持对字符串的引用。

最佳实践是在最内部的范围内声明变量,即在您的情况下为内部循环。

编辑:

添加一些练习。考虑以下示例(请注意我必须编写多少代码,以便优化器至少为我们留下一些东西):

internal class Program
{
    private static string InnerLoop()
    {
        var sb = new StringBuilder();

        string s;
        for (int i = 0; i < 8000; i++)
        {
            s = i.ToString();
            sb.Append(s);
        }

        return sb.ToString();
    }

    private static string OuterLoop()
    {
        var sb = new StringBuilder();

        for (int i = 0; i < 8000; i++)
        {
            string s;
            s = i.ToString();
            sb.Append(s);
        }

        return sb.ToString();
    }

    private static void Main(string[] args)
    {
        Console.WriteLine(InnerLoop());
        Console.WriteLine(OuterLoop());
    }
}

在 Release 中构建,使用 Reflector 打开并查看编译器优化的实际效果:

您可以以类似的方式使用您的代码。

【讨论】:

  • 很好的解释,谢谢!我忘了它实际上是在将它分配给旧字符串之前声明一个新字符串,这是有道理的。
【解决方案3】:

我的问题是,在循环外部和内部声明这些字符串之间会有明显的性能差异吗?

不,不会。您在循环内重新初始化字符串。如果有什么让程序做更多工作的方法是在循环外初始化代码,然后在循环内部再次执行。我猜编译器会优化它。

我知道从技术上讲,每次循环迭代时声明它们会消耗一些额外的时钟周期

您应该让编译器完成它的工作并为您优化它。试图完成编译器的工作只会让您感到沮丧,并使优化代码变得更加困难。这很可能也是完全不真实的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-22
    • 1970-01-01
    • 2011-11-07
    • 2020-09-25
    • 1970-01-01
    • 2021-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多