【问题标题】:C#, Declaring a variable inside for..loop, will it decrease performance? [duplicate]C#,在 for..loop 中声明一个变量,会降低性能吗? [复制]
【发布时间】:2011-12-14 14:33:51
【问题描述】:

例如:

            for (i = 0; i < 100; i++)
            {
               string myvar = "";
                // Some logic
            }

它会导致性能或内存泄漏吗?

我为什么这样做,因为我不希望在 for..loop 之外访问“myvar”。

它是任何性能监视器,我可以比较两个 sn-p 或整个程序之间的执行时间?

谢谢你。

【问题讨论】:

  • C# 中没有内存泄漏。只有循环引用,你不会得到一个字符串。
  • @Dani:.NET 中内存泄漏的可能性较小,但绝对有可能发生。
  • @ScottDorman:有例子吗?
  • @StriplingWarrior:这只是因为保留了对不需要对象的引用。是的,您可以将您曾经使用过的每个对象推送到全局 Stack&lt;Object&gt; 并大喊“C# 已泄漏!!!”但这太愚蠢了,任何语言都无法解决。

标签: c# variables


【解决方案1】:

不,变量纯粹是为了方便程序员。在哪里声明它们并不重要。详情请见my answer to this duplicate question

【讨论】:

  • 一般来说,由于字符串不可变,您不应该在循环内进行字符串操作,因为您每次都会创建一个新字符串。
  • @ScottDorman:朋友,请阅读问题。他不是在询问是否进行字符串操作。他在问关于声明一个变量。
  • 在哪里声明它们很重要,虽然 C# 没有 RAII,但它通过垃圾收集器扫描堆栈来模拟一个。当一个变量在范围内时它不会被收集,所以如果你把它嵌套得很深,它可能会被收集,如果没有嵌套它就不会被收集。
  • @Dani:我会向您推荐我链接中的答案,我在其中显示编译的代码是相同的,无论您在哪里声明变量。当您在那里时,请阅读 Eric Lippert 的评论。
  • 这是一个重复的问题,但我认为大多数提出这个问题的人都不会使用这些关键字。
【解决方案2】:

也许你可以看看我曾经做过的关于另一个对话的旧测试。 Variable declaration. Optimized way

结果证明,重新定义速度更快,但在内存上却不那么容易。

我的简单测试。我初始化了一个对象 100,000,000 次,显然创建一个新对象比重新使用旧对象要快:O

    string output = "";
    {
        DateTime startTime1 = DateTime.Now;
        myclass cls = new myclass();
        for (int i = 0; i < 100000000; i++)
        {
            cls = new myclass();
            cls.var1 = 1;
        }
        TimeSpan span1 = DateTime.Now - startTime1;
        output += span1.ToString();
    }

    {
        DateTime startTime2 = DateTime.Now;
        for (int i = 0; i < 100000000; i++)
        {
            myclass cls = new myclass();
            cls.var1 = 1;
        }
        TimeSpan span2 = DateTime.Now - startTime2;
        output += Environment.NewLine + span2.ToString() ;
    }
    //Span1 took 00:00:02.0391166
    //Span2 took 00:00:01.9331106

public class myclass
{
    public int var1 = 0;
    public myclass()
    {
    }
}

【讨论】:

  • 编译器倾向于大幅优化代码,因此这些结果并没有真正显示任何内容。
  • 即使在相同的代码中,由于缓存和同时在计算机上运行的其他程序等因素,像这样的基准测试几乎总是会显示出一些变化,所以你得到的结果略有不同也就不足为奇了运行这个。我只运行了两次代码,第二次通过两个时间跨度的值完全相同。
【解决方案3】:

我相信这将是一个性能问题。因为 VM 需要分配内存来存储对每个循环的 String 的引用。即使引用可能指向同一个 String 实例,每次循环时都分配内存并不是可取的。

【讨论】:

  • 声明变量不会分配内存。
  • 变量通常可以存储在 CPU 寄存器中,根本不需要使用任何内存。当没有足够的寄存器来保存所有必要的变量时,在调用方法时会在堆栈上分配一个固定位置,并重用该位置。因此,无论您在 for 循环上迭代多少次,在那里分配变量都不会导致分配更多内存。
  • 完整的解释比我在评论中解释的要复杂,但根据经验,如果您在整个方法中声明并使用 50 个变量,您将有大约 50 个内存插槽分配给存储它们,无论变量是否在循环中声明。如果编译器可以看到您一次只使用这 50 个变量,它可以重用相同的内存槽。所以命名变量本身纯粹是为了程序员的方便。堆是一个巨大的内存块,在您调用和从方法返回时会增长和缩小。
  • @StriplingWarrior 谢谢你。每天学些新东西。不知道编译器足够聪明,可以在这些情况下重用内存。
  • @StriplingWarrior 你不是说堆栈在你调用和从方法返回时增长和缩小,而不是堆?
【解决方案4】:

更新:

如果您使用与循环相同类型的变量,则可以执行我最初建议的操作:

for (int  i = 0,  myvar = 0; i < 100; i++) {
    //some logic
}

否则不要像其他人已经建议的那样担心它

@phoog,谢谢你检查答案

【讨论】:

  • 这真的不能解决任何问题,因为您仍在循环内进行字符串操作,由于字符串的不可变性,这可能会变得很昂贵。
  • 你到底是如何从 OP 中得到所有这些的,没有提到字符串操作,他只是要求同时隐藏变量的范围,而不是在每次迭代时声明跨度>
  • 你的 for 循环不会编译。试试吧。不能用 var 声明/初始化多个变量,同一语句中的多个声明必须具有相同的类型。
  • @Kris Ivanov 我删除了我的反对票。作为记录,原始代码类似于for (var i = 0, myvar = ""; ...; ...)
【解决方案5】:

提供一个真实的例子:

我刚写完一个 .obj 模型加载器,当然,它包含一些嵌套循环。 我在循环上方声明了所有变量,但随后我开始想知道与 OP 相同的事情并找到了这个线程。因此,我尝试将所有变量声明移动到循环中使用它们的第一个点,并且实际上看到了小的性能提升。以前平均加载 380 毫秒(实际上是 370-400 毫秒)的模型现在加载速度始终快了大约 15-20 毫秒。这只是大约 5%,但仍然是一个改进。

我的循环结构只包含一个 foreach 循环和一个嵌套的 for 循环,但还包含许多 if 语句。我移动了 5 个变量声明,其中大部分是字符串和整数数组。

【讨论】:

  • 这里感知到的性能提升可能不是来自您所做的代码更改。像这样测量性能很棘手,因为它可能会受到内存压力、其他正在运行的进程和缓存的影响,而这些都在很大程度上超出了您的控制范围。我很想看到一个写得很好的基准,它显示出你所描述的任何差异。
【解决方案6】:

是的,这会产生内存使用问题。我不相信这会导致内存泄漏,因为垃圾收集器最终会收集未使用的对象,但这会对应用程序的性能产生负面影响。

我建议您使用在 for 循环范围之外声明的字符串生成器。

【讨论】:

  • 它是如何产生“内存使用问题”的?字符串是不可变的,因此每个字符串赋值都会产生一个新的字符串对象。 StringBuilder 可能完全不适合在这里使用。
  • @AndrewBarber 内存使用问题将是由于字符串不变性在循环的每次迭代中创建新字符串的结果。每个新字符串都会使用内存,并会导致内存使用量增加,直到垃圾收集器下一次运行。
  • @AndrewBarber:StringBuilder 怎么不合适?字符串是不可变的,因此每次循环都会创建一个新实例,这将导致更高的内存消耗。
  • 字符串是不可变的。声明字符串的位置无关紧要 - 创建一个新实例并且需要 GC'ed。这没什么区别。他这里没有进行聚合串联,所以 StringBuilder 不合适。
  • @StriplingWarrior 如果不进行操作,为什么要在循环范围内初始化变量?如果只是想测试一个空字符串,他最好使用 String.Empty 常量或 String.IsNullOrEmpty 辅助方法。
【解决方案7】:

它不会完全降低性能或导致内存泄漏,但仍需谨慎。

字符串是不可变的,这意味着一旦创建,就无法更改。通过在循环内创建一个新字符串,您至少创建了 n 个字符串变量。如果您尝试在循环内进行字符串操作,则应考虑使用StringBuilder

【讨论】:

  • 我很高兴您提到 StringBuilder,但只是为了澄清一下:string myVar = "" 不会创建新字符串。它只是将变量的起始值声明为指向string.Empty 占用的内存位置。常量字符串值由 .NET 编译器嵌入。
  • 这个问题与字符串操作完全无关
  • @StriplingWarrior:是的,它会被编译器执行。但是,有一个自然的假设,如果在循环中声明了一个字符串变量,它将执行额外的字符串操作,特别是因为它最初被设置为 String.Empty。在这种情况下,一旦有另一个字符串操作,就会创建一个新的字符串实例,因此这可能会导致更高的内存使用。
  • 实际上,因为他以空字符串开始每个循环,我会说这意味着他不会从使用 StringBuilder 中受益。只有当他使用 for 循环在多个迭代中构建单个字符串时,StringBuilder 才会有所帮助。
【解决方案8】:

除非编译器以某种方式优化您的代码,否则在 for 循环中声明变量将需要分配新变量并收集旧变量。话虽如此,编译器在优化代码方面做得非常好。

如果您想要一种快速的方法来测试您的两个场景,请使用 StopWatch 类来衡量执行每个案例所需的时间。我的猜测是,这种差异将不存在甚至可以忽略不计。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-07
    • 2018-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-27
    • 2023-03-18
    相关资源
    最近更新 更多