【问题标题】:Performance issues when declaring variable声明变量时的性能问题
【发布时间】:2014-05-05 19:32:06
【问题描述】:

在下一种情况下声明新变量是否有任何性能成本:

这是一个例子,只是为了说明这一点。

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

我还有下一个方法:

选项 1:

public void MyMethod(Person person)
{
    if (person.FirstName.Contains("Ro") || (person.LastName.StartsWith("A") && person.Age > 20))
    {
        //Do something
    }
    else if (person.FirstName.Contains("Ko") || (person.LastName.StartsWith("B") && person.Age >= 40))
    {
        //Do something
    }
    else if (person.FirstName.Contains("Mo") || (person.LastName.StartsWith("C") && person.Age > 60))
    {
        //Do something
    }
    else
    {
        //Do something
    }
}

选项 2:

public void MyMethod(Person person)
{
    string firstName = person.FirstName;
    string lastName = person.LastName;
    int age = person.Age;

    if (firstName.Contains("Ro") || (lastName.StartsWith("A") && age > 20))
    {
        //Do something
    }
    else if (firstName.Contains("Ko") || (lastName.StartsWith("B") && age >= 40))
    {
        //Do something
    }
    else if (firstName.Contains("Mo") || (lastName.StartsWith("C") && age > 60))
    {
        //Do something
    }
    else
    {
        //Do something
    }
}

同样,这只是一个例子来展示问题的想法。

问题:选项 1 和选项 2 之间是否存在性能或内存问题?

当然,选项 2 看起来更好,更易读。

【问题讨论】:

  • 当然,但它是微不足道的。我总是会选择可读性而不是微优化。

标签: c# performance


【解决方案1】:

这是由抖动解决的,它积极地消除方法的局部变量,并寻找将它们存储在 CPU 寄存器中的方法。无论您是否自己声明变量,它都会这样做。您的属性吸气剂很简单,没有副作用,抖动可以自己发现。那些 getter 方法也被淘汰了,它们是内联的。实际上,它会将您的代码从第一个 sn-p 转换为第二个 sn-p。

这就是你无法通过Reflection找出一个方法有哪些局部变量的核心原因。以及为什么您在调试优化代码时遇到问题。以及为什么在 C# 中存在 volatile 关键字。当抖动优化器完成后,局部变量就不再存在了。

您可以在this answer 中找到关于抖动执行的优化类型的概述。

所以,不,不要犹豫,以这种方式使您的代码更具可读性,预计不会对性能产生任何影响。但是请记住,您可以在代码中引入错误,当您的其余代码影响您正在使用的对象时,它将触发。您当然会存储该属性的陈旧值。如果类不重要并且属性 getter 本身具有副作用,这并不总是那么明显。否则,.NET 中的编码准则要求具有副作用的属性应该是方法的核心原因。

【讨论】:

  • 我想你可能误解了C#中volatile的含义。有关详细信息,请参阅blogs.msdn.com/b/ericlippert/archive/2011/06/16/…
  • 我不是。我认为微软员工永远不会真正从不得不处理安腾处理器中恢复过来。在 x86 上,它可以防止抖动将变量保存在 CPU 寄存器中,而无需从内存中刷新它。在 x64 上它根本没有效果。使用 volatile 任意更改具有弱内存模型的处理器的内存语义是一个大错误。足以让 Joe Duffy 惊呼volatile is evil! 是。
  • 我知道汉斯。我知道你知道,我只是没有真正注意到你写了这个答案。但是您说“以及为什么在 C# 中存在 volatile 关键字。当抖动优化器完成后,这样的变量根本不再存在。”对我来说,这听起来好像您说 volatile 变量已被优化掉。显然情况并非如此。事实上,易失性(局部)变量甚至不存在,易失性字段存在。您可以考虑重新措辞以确保没有人感到困惑。
【解决方案2】:

嗯,我发现选项 1 更具可读性。选项 2 意味着您正在操纵这些属性的值。解析代码需要一两秒钟才能确定你不是。

所以这只是一种风格选择。请查阅您的风格指南,了解您的组织或特定代码库更喜欢哪种风格。

性能差异将完全为零。你可能不相信我,如果不相信,唯一确定的方法就是为自己实际进行基准测试。但这真的是浪费时间。

属性 getter 应该(除非文档另有说明)具有恒定时间执行,因此查询属性的值应该与查询局部变量的值没有什么不同。因此,唯一可能影响性能的另一件事是,使用选项 2,您可能最终会查询 all 属性的值,而您的代码可能永远不会到达需要最后一个姓名或年龄。但这可能会有很大的变化。您实际上只能通过使用一些真实数据重复基准测试来确定这一点。除非这种方法真的被证明是一个瓶颈,否则不要浪费你的时间。而且不会。

如果您要根据可读性以外的某些基础做出决定,那将是线程安全的。如果属性 getter 返回的值可能会在另一个线程修改对象后发生变化,那么使用选项 1 可能会遇到问题。这会使将这些属性的值缓存到局部变量中更可取,因此您会选择选项 2 .

但这正是为什么编译器要进行任何类型的缓存,将选项 1 转换为选项 2,正如其他一些答案所暗示的那样。生成的代码会有所不同,性能差异不会很大。 (尽管 JITter 肯定会在运行时执行这种类型的优化,正如 Hans 在他的回答中指出的那样。)

【讨论】:

  • 关于基于分配给当地人的可能假设的好点。
【解决方案3】:

两者的复杂性相同。所以我认为两者在这两个方面都是平等的。

【讨论】:

    【解决方案4】:

    实际上,性能不会有任何差异,我怀疑即使您运行代码十亿次,您也无法衡量任何差异。

    话虽如此,但还是有区别的。一个属性获取操作其实就是一个方法调用,所以第一个操作会在访问属性时调用 person.get_FirstName 方法。这意味着,根据编译器优化代码的方式,代码的行为方式可能会有所不同。

    因此不会有任何可衡量的差异,您应该选择最易读的选项。 :-)

    【讨论】:

    • 我刚刚遇到过类似的情况,在运行 ~100.000/s 的方法中使用了两次相同的属性,并且在进行本地复制时只调用一次 getter 时存在可测量的差异。
    • 这是使用默认属性实现还是该属性实现了一些实际逻辑?例如,如果属性调用 Web 服务,那么情况就完全不同了。
    • 不,简单的自动属性。使用 dotTrace 测量时间。
    • 这很有趣。原因可能是属性getter真正做的方法调用造成的开销。
    • @chrfin 启用或不启用优化?
    猜你喜欢
    • 2011-06-11
    • 1970-01-01
    • 2011-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-04
    • 2023-02-01
    • 1970-01-01
    相关资源
    最近更新 更多