字段与属性。性能优化

请注意这个问题只与性能有关。让我们跳过设计原则、哲学、兼容性、可移植性和任何与纯性能无关的东西。谢谢你。

现在回到问题。我总是假设,因为 C # getters/setter 实际上是伪装的方法,所以读取 public 字段必须比调用 getter 更快。

因此,为了确保我做了一个测试(下面的代码)。但是这个测试只能产生预期的结果(即 字段的速度比吸气剂的速度快,达到34% ) 如果,您可以在 VisualStudio 中运行它。

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

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

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

请帮助我证明或反驳在现实生活中属性比字段慢的观点。

问题是——我应该如何修改测试类以使 CLR 更改行为,从而使公共字段的性能优于 getter。OR 告诉我,任何没有内部逻辑的属性将执行与字段相同的操作(至少在 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;
}
}
37537 次浏览

The only explanation could be is that the CLR does additional optimisation (correrct me if I am wrong here).

Yes, it is called inlining. It is done in the compiler (machine code level - i.e. JIT). As the getter/setter are trivial (i.e. very simple code) the method calls are destroyed and the getter/setter written in the surrounding code.

This does not happen in debug mode in order to support debugging (i.e. the ability to set a breakpoint in a getter or setter).

In visual studio there is no way to do that in the debugger. Compile release, run without attached debugger and you will get the full optimization.

I do not believe that in real application where those properties being used in much more sophisticated way they will be optimised in the same way.

The world is full of illusions that are wrong. They will be optimized as they are still trivial (i.e. simple code, so they are inlined).

The JIT will inline any method (not just a getter) that its internal metrics determine will be faster inlined. Given that a standard property is return _Property; it will be inlined in every case.

The reason you are seeing different behavior is that in Debug mode with a debugger attached, the JIT is significantly handicapped, to ensure that any stack locations match what you would expect from the code.

You are also forgetting the number one rule of performance, testing beats thinking. For instance even though quick sort is asymptotically faster than insertion sort, insertion sort is actually faster for extremely small inputs.

As others have already mentioned, the getters are inlined.

If you want to avoid inlining, you have to

  • replace the automatic properties with manual ones:

    class A
    {
    private double p;
    public double P
    {
    get { return p; }
    set { p = value; }
    }
    }
    
  • and tell the compiler not to inline the getter (or both, if you feel like it):

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

Note that the first change does not make a difference in performance, whereas the second change shows a clear method call overhead:

Manual properties:

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

No inlining of the getter:

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

Have a look at the Properties vs Fields – Why Does it Matter? (Jonathan Aneja) blog article from one of the VB team members on MSDN. He outlines the property versus fields argument and also explains trivial properties as follows:

One argument I’ve heard for using fields over properties is that “fields are faster”, but for trivial properties that’s actually not true, as the CLR’s Just-In-Time (JIT) compiler will inline the property access and generate code that’s as efficient as accessing a field directly.

It should be noted that it's possible to see the "real" performance in Visual Studio.

  1. Compile in Release mode with Optimisations enabled.
  2. Go to Debug -> Options and Settings, and uncheck "Suppress JIT optimization on module load (Managed only)".
  3. Optionally, uncheck "Enable Just My Code" otherwise you may not be able to step in the code.

Now the jitted assembly will be the same even with the debugger attached, allowing you to step in the optimised dissassembly if you so please. This is essential to understand how the CLR optimises code.

After read all your articles, I decide to make a benchmark with these code:

    [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;
}
}

When test in debug mode, I got this result:

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

but when switch to release mode, the result is different than before.

auto: 2161291, 873.91%.
field: 2886444, 654.36%.
manual: 2252287, 838.60%.
no inlining: 18887768, 100.00%.

seems auto property is a better way.