Field vs Property。优化性能

时间:2012-03-23 16:30:01

标签: c# .net performance c#-4.0 optimization

请注意此问题仅与演出有关。让我们跳过设计指南,理念,兼容性,可移植性以及与纯性能无关的任何内容。谢谢。

现在回答这个问题。我一直认为,因为C#getters / setter实际上是伪装的方法,所以读取公共字段必须比调用getter更快。

所以要确保我做了一个测试(下面的代码)。但是,此测试仅产生预期结果(即字段比34%的getter更快)如果从Visual Studio内部运行它。

从命令行运行它后,显示几乎相同的时间......

唯一的解释可能是CLR会进行额外的优化(如果我在这里错了,请纠正我)。

我不相信在实际应用中,以更复杂的方式使用这些属性,它们将以相同的方式进行优化。

请帮助我证明或反驳现实生活中的属性比田地慢的想法。

问题是 - 如何修改测试类以使CLR更改行为,以便公共字段超越getter。或者告诉我,任何没有内部逻辑的属性都会像字段一样执行(至少在getter上)

编辑:我只谈论发布x64版本。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace PropertyVsField
{
    class Program
    {
        static int LEN = 20000000;
        static void Main(string[] args)
        {
            List<A> a = new List<A>(LEN);
            List<B> b = new List<B>(LEN);

            Random r = new Random(DateTime.Now.Millisecond);

            for (int i = 0; i < LEN; i++)
            {
                double p = r.NextDouble();
                a.Add(new A() { P = p });
                b.Add(new B() { P = p });
            }

            Stopwatch sw = new Stopwatch();

            double d = 0.0;

            sw.Restart();
            for (int i = 0; i < LEN; i++)
            {
                d += a[i].P;
            }

            sw.Stop();

            Console.WriteLine("auto getter. {0}. {1}.", sw.ElapsedTicks, d);

            sw.Restart();
            for (int i = 0; i < LEN; i++)
            {
                d += b[i].P;
            }

            sw.Stop();

            Console.WriteLine("      field. {0}. {1}.", sw.ElapsedTicks, d);

            Console.ReadLine();
        }
    }

    class A
    {
        public double P { get; set; }
    }
    class B
    {
        public double P;
    }
}

6 个答案:

答案 0 :(得分:51)

正如其他人已经提到的那样,getter是内联

如果你想避免内联,你必须

  • 用手动属性替换自动属性:

    class A 
    {
        private double p;
        public double P
        {
            get { return p; }
            set { p = value; }
        }
    } 
    
  • 并告诉编译器不要内联getter(或两者,如果你愿意的话):

            [MethodImpl(MethodImplOptions.NoInlining)]
            get { return p; }
    

请注意,第一个更改不会对性能产生影响,而第二个更改会显示明确的方法调用开销:

手动属性:

auto getter. 519005. 10000971,0237547.
      field. 514235. 20001942,0475098.

没有内联吸气剂:

auto getter. 785997. 10000476,0385552.
      field. 531552. 20000952,077111.

答案 1 :(得分:24)

查看来自MSDN上的一个VB团队成员的Properties vs Fields – Why Does it Matter? (Jonathan Aneja)博客文章。他概述了属性与字段的参数,并解释了如下的微不足道的属性:

  

我听说过在属性上使用字段的一个论点是   “字段更快”,但对于实际上没有的琐碎属性   是的,因为CLR的Just-In-Time(JIT)编译器将内联   属性访问并生成与访问a一样高效的代码   直接领域。

答案 2 :(得分:12)

JIT将内联任何方法(不仅仅是一个getter),其内部指标确定的内联更快。鉴于标准属性为return _Property;,它将在每种情况下都内联。

您看到不同行为的原因是,在附加调试器的调试模式下,JIT非常缺陷,以确保任何堆栈位置与您对代码的期望相匹配。

你也忘记了表现的头号规则,测试节拍思维。例如,即使快速排序渐远比插入排序快,但对于极小的输入,插入排序实际上更快。

答案 3 :(得分:6)

  

唯一可能的解释是CLR会进行额外的优化(如果我在这里错了,请与我联系)。

是的,它被称为内联。它在编译器中完成(机器代码级 - 即JIT)。由于getter / setter是微不足道的(即非常简单的代码),方法调用被破坏,getter / setter被写入周围的代码中。

在调试模式下不会发生这种情况以支持调试(即在getter或setter中设置断点的能力)。

在visual studio中,无法在调试器中执行此操作。编译版本,在没有附加调试器的情况下运行,您将获得完整的优化。

  

我不相信在实际应用中,以更复杂的方式使用这些属性,它们将以相同的方式进行优化。

世界充满了错误的幻想。它们将被优化,因为它们仍然是微不足道的(即简单的代码,因此它们被内联)。

答案 4 :(得分:3)

应该注意的是,可以在Visual Studio中看到“真实”的性能。

  1. 在启用Optimisations的发布模式下编译。
  2. 转到调试 - &gt;选项和设置,取消选中“在模块加载时禁止JIT优化(仅限管理)”。
  3. 或者,取消选中“启用我的代码”,否则您可能无法进入代码。
  4. 现在即使连接了调试器,jitted程序集也是一样的,如果你愿意,可以让你进入优化的反汇编程序。这对于理解CLR如何优化代码至关重要。

答案 5 :(得分:0)

阅读完所有文章后,我决定使用以下代码作为基准:

<%= text_field(:doc, :email, {:value=>current_user.try(:email),:class=>"form-control"})%>

在调试模式下进行测试时,我得到了以下结果:

    [TestMethod]
    public void TestFieldVsProperty()
    {
        const int COUNT = 0x7fffffff;
        A a1 = new A();
        A a2 = new A();
        B b1 = new B();
        B b2 = new B();
        C c1 = new C();
        C c2 = new C();
        D d1 = new D();
        D d2 = new D();
        Stopwatch sw = new Stopwatch();

        long t1, t2, t3, t4;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            a1.P = a2.P;
        }

        sw.Stop();

        t1 = sw.ElapsedTicks;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            b1.P = b2.P;
        }

        sw.Stop();


        t2 = sw.ElapsedTicks;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            c1.P = c2.P;
        }

        sw.Stop();

        t3 = sw.ElapsedTicks;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            d1.P = d2.P;
        }

        sw.Stop();


        t4 = sw.ElapsedTicks;
        long max = Math.Max(Math.Max(t1, t2), Math.Max(t3, t4));

        Console.WriteLine($"auto: {t1}, {max * 100d / t1:00.00}%.");
        Console.WriteLine($"field: {t2}, {max * 100d / t2:00.00}%.");
        Console.WriteLine($"manual: {t3}, {max * 100d / t3:00.00}%.");
        Console.WriteLine($"no inlining: {t4}, {max * 100d / t4:00.00}%.");

    }
    class A
    {
        public double P { get; set; }
    }
    class B
    {
        public double P;
    }
    class C
    {
        private double p;
        public double P
        {
            get => p;
            set => p = value;
        }
    }
    class D
    {
        public double P
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            get;
            [MethodImpl(MethodImplOptions.NoInlining)]
            set;
        }
    }

但是当切换到释放模式时,结果与以前不同。

auto: 35142496, 100.78%.
field: 10451823, 338.87%.
manual: 35183121, 100.67%.
no inlining: 35417844, 100.00%.

似乎自动属性是一种更好的方法。