Field vs Property.优化性能

Bop*_*Bop 64 .net c# optimization performance c#-4.0

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

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

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

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

唯一的解释可能是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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Hei*_*nzi 54

正如其他人已经提到的,吸气剂是内联的.

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

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

手动属性:

auto getter. 519005. 10000971,0237547.
      field. 514235. 20001942,0475098.
Run Code Online (Sandbox Code Playgroud)

没有内联吸气剂:

auto getter. 785997. 10000476,0385552.
      field. 531552. 20000952,077111.
Run Code Online (Sandbox Code Playgroud)

  • 只是想知道,你需要使用手动属性还是将属性应用于自动属性getter仍然有效? (4认同)
  • 这些数字是多少? (3认同)

Jam*_*See 24

看看属性与字段 - 为什么重要?(Jonathan Aneja)来自MSDN上的一个VB团队成员的博客文章.他概述了属性与字段参数,并解释了如下的简单属性:

我听说过在属性上使用字段的一个论点是"字段更快",但对于实际上不正确的琐碎属性,因为CLR的实时(JIT)编译器将内联属性访问并生成如下代码高效直接访问字段.


Guv*_*nte 12

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

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

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

  • 太棒了......说得好......"测试节拍".谢谢. (2认同)
  • @PaulChilds:您将相对陈述视为绝对,我不知道为什么。当谈论微优化时,您是否有足够努力思考来优化 CLR 代码的能力值得怀疑。了解 C# 编译器、JIT 甚至 CPU 可能进行哪些性能优化是一个非常复杂的主题,对于“使用属性与字段是否会产生性能成本”等简单主题来说不值得。如果您谈论的是算法,那么是的,请做好功课。无论哪种情况,任何未经测量的性能改进都是值得怀疑的。这与我的观点一致。 (2认同)
  • @PaulChilds:通过任何合理的衡量标准,测试都是性能的第一条规则。您认为思想对于性能调优更重要,这是完全错误的。这个问题显然不是性能设计,性能设计是唯一可以更好思考的空间。 (2认同)

小智 6

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

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

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

在visual studio中,无法在调试器中执行此操作.编译版本,无需附加调试器即可运行,您将获得完整的优化.

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

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


dex*_*ang 6

阅读了您的所有文章后,我决定使用这些代码进行基准测试:

    [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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

auto: 35142496, 100.78%.
field: 10451823, 338.87%.
manual: 35183121, 100.67%.
no inlining: 35417844, 100.00%.
Run Code Online (Sandbox Code Playgroud)

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

auto: 2161291, 873.91%.
field: 2886444, 654.36%.
manual: 2252287, 838.60%.
no inlining: 18887768, 100.00%.
Run Code Online (Sandbox Code Playgroud)

看来汽车财产是一个更好的方式。


Asi*_*sik 5

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

  1. 在启用优化的发布模式下编译。
  2. 转到“调试”->“选项和设置”,然后取消选中“抑制模块加载时的 JIT 优化(仅限托管)”。
  3. 或者,取消选中“仅启用我的代码”,否则您可能无法单步执行代码。

现在,即使附加了调试器,抖动的程序集也将是相同的,如果您愿意,您可以进入优化的反汇编。这对于理解 CLR 如何优化代码至关重要。

  • 但是,如果您想调试并查看某些内容是否内联,则必须将调试器作为单独的步骤附加。通过取消选中这两个选项,您只需按 F5 即可调试优化的构建并单步执行生成的汇编代码。 (3认同)