【问题标题】:Scope of Variables: Instantiation Inside or Outside Loop变量范围:循环内或循环外的实例化
【发布时间】:2014-10-16 01:55:03
【问题描述】:

通过我昨天发布的一个问题,很明显,在循环外部声明一个变量并在内部实例化它与简单地将声明移动到循环内部相比没有性能优势,因此声明和实例化是同时完成的.但是实例化呢?请考虑以下两个选项。

//OPTION 1:
while (_doWork)
{
    Person p = new Person(name = "John", age = 35);
    //work that involves reading (not writing to) p
}

//OPTION 2:
Person p = new Person(name = "John", age = 35);
while (_doWork)
{
    //work that involves reading (not writing to) p
}

就本问题而言,关键假设如下:

  1. p 在循环之外不需要
  2. p 也没有写出来(所以结果明智,两者是一样的)
  3. 我们没有理由继续重新实例化 pp 在两个选项中看起来都一样)

问题:哪个性能更好,哪个选项更好?

这篇文章的答案(尽管是关于声明,而不是实例化)似乎表明后者:Declaring variables inside or outside of a loop

我的想法:

  • 对我来说,继续重新实例化它似乎是一种重大浪费。对于原始类型可能没问题,但对于复杂类?
  • int a = 0 和 int a = new int() 一样,所以我猜上面的答案也适用于原始类型?

【问题讨论】:

  • 这完全取决于你想做什么。
  • 除了重复之外,这个问题似乎主要是基于意见的,并且可能是 2014 o.O 代码审查的候选对象
  • 嗯,int 的分配速度非常快(无论如何它只是在堆栈上保留位置 + 初始化)。对于引用类型,主要取决于您的代码预期它是相同的实例还是不同的实例。
  • 它不会在每个循环中恢复...它被编译为相同的 IL,因此在速度方面没有差异...这留下了可读性。

标签: c# .net performance instantiation


【解决方案1】:

如果你做某事 10 次,它的性能会比你只做一次的要差,因此单个实​​例化更有效。如果您只需要一个实例,那么多次初始化一个对象也是没有意义的。这也可能产生副作用,使事情变得更糟。

所以,如果它在循环中没有改变,那么总是在循环之外声明和初始化它。这也将增加可读性,因为每个人都可以立即看到该对象在接下来的循环中很可能不会改变。

Person p = new Person("John", 35);
while (_doWork)
{
    //work that involves reading (not writing to) p
}

如果你必须在循环中创建对象,你应该在循环中声明它。但不是因为它性能更高,但更具可读性。

阅读:https://softwareengineering.stackexchange.com/questions/56585/where-do-you-declare-variables-the-top-of-a-method-or-when-you-need-them

【讨论】:

  • 非常感谢蒂姆,这正是我正在寻找的答案。因此,基本上以尽可能窄的范围声明、实例化和初始化的概念(请参阅我的帖子中的链接)并不适用,因为在不需要时继续实例化它是没有意义的..
  • @Magnus:完全正确。如果你只需要做一次,那么做10次是没有意义的。不要混淆声明和初始化。
【解决方案2】:

哪个性能更好,哪个选项更好?

根据您的问题,如果只需要一个 Person 实例,则没有逻辑理由一遍又一遍地实例化它 循环内。在循环之外分配一次并在内部使用它。

关于原始类型,您应该区分值类型(intlongdouble)和引用类型(string)。值类型在堆栈上分配,而引用类型在堆上分配。因此,对于您的 Person 类,我们必须在堆内分配正确数量的字节,而在堆栈上分配 int 是更快的分配。

除了最佳实践之外,如果您想知道两者中哪一个具有更好的性能,请对您的代码进行基准测试。这是在我的 Intel Core i5 M430 上使用 .NET Framework 4.5 VS2013 在Debug Mode 中运行的输出:

public class Person
{
    public Person(string name, int age)
    {
        Age = age;
        Name = name;
    }

    public int Age { get; set; }
    public string Name { get; set; }
}

private static void Main(string[] args)
{
   Stopwatch sw = new Stopwatch();
   sw.Start();
   for (int i = 0; i < 1000000; i++)
   {
      Person p = new Person("John", 35);
      var age = p.Age;
      var name = p.Name;
   }

   Console.WriteLine("Loop with inner allocation took {0}", m sw.Elapsed);

   sw.Restart();
   Person px = new Person("John", 35);

   for (int i = 0; i < 1000000; i++)
   {
      var age = px.Age;
      var name = px.Name;
   }

   Console.WriteLine("Loop with outter allocation took {0}", sw.Elapsed)
}

结果:

Loop with inner allocation took 00:00:00.0708861
Loop with outter allocation took 00:00:00.0155522

【讨论】:

  • @PaulZahra 我不确定你在说什么?
  • 太好了,谢谢。也感谢测试速度的代码,如果使用的是值类型 (int),我将使用它来比较两者的不同之处。我认为同样的原则也应该适用于值类型(尽管它更快)?
  • 这是它的理想选择...ideone.com/kYi0FV 这表明它要慢得多,大约 35 倍!内部分配循环占用 00:00:00.0541856 外部分配循环占用 00:00:00.0015612
  • @YuvalItzchakov 虽然引用类型总是在堆上,但值类型并不总是在堆栈上,如果在方法中声明它会在堆栈上,但如果在外部声明一个方法,但在引用类型中,然后它以引用类型进入堆。
  • @PaulZahra - 在方法内分配值类型时,我明确指出。我会修正我的答案以明确这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-14
  • 2013-02-25
  • 2016-01-24
  • 2016-01-14
  • 2012-06-12
  • 1970-01-01
相关资源
最近更新 更多