【问题标题】:Performance overhead for properties in .NET.NET 中属性的性能开销
【发布时间】:2011-03-16 22:37:36
【问题描述】:

我在某处读到,拥有公共属性比在类中拥有公共成员更可取。

  1. 这仅仅是因为抽象和模块化吗?还有其他压倒一切的原因吗​​?

  2. 编译器会将属性访问合并到函数调用中。对于没有备份存储的属性(例如public string UserName { get; set; }),与直接成员访问相比,性能开销是多少? (我知道这通常不会产生影响,但在我的某些代码中,属性被访问了数百万次。)

编辑1: 我在整数成员和属性上运行了一些测试代码,公共成员的速度大约是属性的 3-4 倍。 (调试中的约 57 毫秒与约 206 毫秒以及发布中的 57 与 97 是最常见的运行值)。对于 1000 万次读取和写入,两者都小到不足以证明更改任何内容是合理的。

代码:

    class TestTime1
{
    public TestTime1() { }
    public int id=0;
}
class TestTime2
{
    public TestTime2() { }
    [DefaultValue(0)]
    public int ID { get; set; }
}


class Program
{
    static void Main(string[] args)
    {
        try
        {
            TestTime1 time1 = new TestTime1();
            TestTime2 time2 = new TestTime2();
            Stopwatch watch1 = new Stopwatch();
            Stopwatch watch2 = new Stopwatch();
            watch2.Start();
            for (int i = 0; i < 10000000; i++)
            {
                time2.ID = i;
                i = time2.ID;
            }
            watch2.Stop();
            watch1.Start();
            for (int i = 0; i < 10000000; i++)
            {
                time1.id = i;
                i = time1.id;
            }
            watch1.Stop();
            Console.WriteLine("Time for 1 and 2 : {0},{1}",watch1.ElapsedMilliseconds,watch2.ElapsedMilliseconds);

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        Console.In.ReadLine();
    }
}

【问题讨论】:

  • 序列化和DataBinding是我能想到的原因
  • 未优化调试版本的因素差异无关紧要。您没有将调试版本发送给客户。另外,我注意到在您的测试中,您正在测量 属性的 jit 时间 以及 访问时间。如果您对 jitting 属性的 amortized 成本(包括它的启动时间)感兴趣,那是一回事。但是,如果您感兴趣的是 每次使用 成本,那么 不要将 jit 成本与每次使用成本混为一谈,就像您在此处所做的那样。不管你的测量技术是否好:优化最慢的东西。我怀疑是这个。

标签: c# .net performance


【解决方案1】:

如果您想要一个特定的示例,说明您无法使用常规成员变量执行某些属性,请考虑继承:如果一个类使用公共成员,则该类的派生无法实现验证或其他 getter/二传手行为。他们坚持使用变量,如果他们想做一些不同的事情,他们必须 1) 忽略现有的成员变量并创建一个新属性,2) 添加一个新属性,以及 3) 覆盖每个方法调用或依赖成员变量来使用该属性。这不仅不必要地增加了工作量,而且如果编写派生类的人无法访问源代码,这几乎是不可能的。

如果基类使用属性而不是成员变量,那么只需将验证或其他行为添加到 get/set 函数中,就可以了。

【讨论】:

    【解决方案2】:

    确保您使用 Ctrl-F5 而不是 F5 运行;否则调试器仍将附加,并且某些优化可能无法正常工作,即使在发布模式下也是如此。至少在我的机器上是这样:F5 给出的结果与您发布的内容相似,而 Ctrl-F5 给出的结果相同。

    【讨论】:

      【解决方案3】:

      我之前问过same question

      我猜您使用的是 VS2008,使用的是 64 位操作系统并且编译设置为“任何 CPU”?如果是这样,x64 JIT 编译器不会内联属性。它们在 32 位上运行,使其在性能上与公共字段相同。

      【讨论】:

        【解决方案4】:

        这仅仅是因为抽象和模块化吗?还有其他压倒一切的原因吗​​?

        我不知道;这些理由本身就足够令人信服。但也许其他人会参与进来。

        属性访问被编译器整合到函数调用中。对于没有备份存储的属性(例如 public string UserName { get; set; }),与直接成员访问相比,性能开销是多少? (我知道这通常不会产生影响,但在我的某些代码中,属性被访问了数百万次。)

        在生成的中间语言中,属性访问被转换为方法调用。然而,正如这个词所说,这只是一种中间语言:它被即时编译成其他东西。此转换步骤还涉及优化,例如简单方法的内联,例如简单的属性访问器。

        我希望(但您需要测试以确保)JITter 会处理此类访问器,因此应该没有性能差异。

        【讨论】:

        • 这是一个原因:.NET 标准规定您不得将字段公开为公共字段,只有极少数例外。至于内联,请参阅我对 Adam 的评论。
        • 标准规定了这一点是有原因的,我认为这是我们正在寻找的原因,而不是“因为微软这么说”:)
        • 我会强调(例如加粗)内联,因为 AFAIK 在 Mono 和 CLR 上的所有属性都是如此。
        • @Thomas:是的,绝对!我并不是说这个标准是武断的或适得其反的。我只是说它是一个标准这一事实本身就是一个很好的理由,所有事情(或多或少)都是平等的,这样做。由于性能考虑无关紧要,我认为模糊其他条件不变的标准是满足的。 :-)
        • 这里有一个关于内联问题的更权威的答案(带链接):stackoverflow.com/questions/646779/does-c-inline-properties/…
        【解决方案5】:

        连续运行 20 次测试,确保在 Release 构建中启用 JIT 优化:

        Time for 1 and 2 : 47,66
        Time for 1 and 2 : 37,42
        Time for 1 and 2 : 25,36
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 27,25
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 26,25
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 25,25
        Time for 1 and 2 : 25,25
        

        是的,JITter 擅长内联属性访问器。性能不是问题,永远不应考虑。

        【讨论】:

        • 嗯...在调试而不是发布中进行了测试。我的错!
        • 在发布时,我的机器上仍有 80% 的开销。我的机器是 32 位的,我看到 32 位和 64 位之间存在一些奇怪的性能差异。
        • 我的也是 32 位的,我假设我们使用相同的 JITter。工具+选项,调试,常规,取消勾选“Suppress JIT optimization on module load”。
        【解决方案6】:

        在我发了this的帖子后,我意识到这基本上是为了隐藏你的对象的内部运作。

        【讨论】:

          【解决方案7】:

          1) 其用于封装原则,但其他 .NET 功能使用数据绑定等属性。

          2) 我不确定我是否同意, 我一直听说如果属性是直接获取/设置,它的速度与标准字段访问一样快 - 编译器会这是给你的。

          更新: 似乎两者兼而有之,编译为方法调用,但经过 JIT 优化。无论哪种方式,这种性能问题都不会对您的代码产生有意义的影响。但是请注意,有关实现属性的指导是使它们尽可能轻量级,调用者并不认为它们很昂贵。

          【讨论】:

          • 这里有更多细节。例如,我相信它只能在调用者和属性在同一个程序集中定义时内联调用。但是,真的,没关系。与使用尽可能多的逻辑替换基础字段所获得的收益相比,调用开销微不足道。
          • 奇怪,这是一个相当奇怪的细节,因为没有内联 - 在某种程度上令人失望,哈哈。如前所述,无论是否编译,性能下降都可以忽略不计
          • 同意性能无关,但我认为限制是有道理的。在为属性调用生成 CIL 代码时,编译器需要能够访问实现,以便它可以确认只是分配给一个支持变量,以便它可以识别该变量。换句话说,这种优化是原始编译的一部分,而不是最终的 JIT。
          • 嗯,确实有道理。
          • 冒着重复自己的风险,这里有一个更权威答案的链接:stackoverflow.com/questions/646779/does-c-inline-properties/…
          【解决方案8】:

          完全不用担心性能开销。它太小了,你不应该考虑削弱类的封装;这将是最差排序的过早优化。

          【讨论】:

            【解决方案9】:

            它主要用于抽象目的(您可以稍后添加验证而无需破坏现有代码或需要重新编译)。

            即使使用自动属性,编译器仍然会生成一个支持字段,并且会按此执行。

            【讨论】:

              猜你喜欢
              • 2010-12-14
              • 2016-12-19
              • 2023-04-01
              • 1970-01-01
              • 1970-01-01
              • 2018-07-26
              • 1970-01-01
              • 2013-02-09
              • 1970-01-01
              相关资源
              最近更新 更多