105

Please note this question related to performance only. Lets skip design guidelines, philosophy, compatibility, portability and anything what is not related to pure performance. Thank you.

Now to the question. I always assumed that because C# getters/setters are really methods in disguise then reading public field must be faster than calling a getter.

So to make sure I did a test (the code below). However this test only produces expected results (ie fields are faster than getters at 34%) if you run it from inside Visual Studio.

Once you run it from command line it shows pretty much the same timing...

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

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.

Please help me to prove or disprove the idea that in real life properties are slower than fields.

The question is - how should I modify the test classes to make the CLR change behaviour so the public field outperfroms the getters. OR show me that any property without internal logic will perform the same as a field (at least on the getter)

EDIT: I am only talking about Release x64 build.

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; } } 
8
  • 24
    Bobb, Microsoft knows very well that no matter how much they trumpet design philosophy/shmilosophy, everybody would use public fields instead of properties once they realize that it's whooping 34% faster (even though it's a micro-optimization in 99.9% of the cases, people would do it anyway). So the Eric Lipperts of the world have built a darn good compiler and optimizer that figures out that your automatic property is a public field in disguise, and optimize accordingly. Commented Mar 23, 2012 at 16:38
  • 7
    You may want to try making B a struct instead of a class if you're using just a container with no methods. You also need to take into account whether you are doing Start Debugging or Start Without Debugging when launching it inside Visual Studio. Using Start Debugging hooks a lot of things to allow yout to do stepping, watch values, etc. and so can have a significant performance impact of it's own. Commented Mar 23, 2012 at 16:38
  • 1
    @dasblinkenlight - good point... but it seems that the goodies are coming from CLR not C# compiler... If it was compiler then it will be as fast in the VS too.... am I missing here someth? Commented Mar 23, 2012 at 16:45
  • 1
    If you're using a property a lot in a tight loop then it may be faster to store it in a local variable. see jacksondunstan.com/articles/2968 Commented Nov 1, 2017 at 0:20
  • 1
    Your request says "OR", but the two options are not mutually exclusive. You have marked an answer that went to lengths to jump through hoops to degrade performance to meet your first task, but readers should be warned that this is not useful (at least in terms of the pure performance you have stressed) and that inlined auto-properties are performant Commented Apr 6, 2021 at 22:50

6 Answers 6

73

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. 
Sign up to request clarification or add additional context in comments.

7 Comments

Just wondering, do you need to use a manual property or would applying the attribute to the automatic property getter still work?
Is there a reason one would ever want to prevent inlining?
It's old, but I'm still curious about the question @JacobStamm brought up, if anyone comes across this. I'm not sure it has a small enough scope to warrant its own question.
What are these numbers?
@person27: The output of running OP's code. The first numeric column is the relevant one and contains the number of ticks passed. Smaller is better (i.e. faster).
|
34

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.

2 Comments

Hi @Bobb, I don't doubt that you can read. I actually like Heinzi's answer best as well. I just figured this was a useful little reference and would make a good contribution to the topic in general since the explanation comes from a primary source.
thanks mate .. it was Friday joke dont worry. I marked Heinzi because I saw his answer few minutes earlier than yours. but i like yours too, cheers ! :-)
14

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.

5 Comments

Great...well said ..." testing beats thinking". Thanks.
"Testing beats thinking" is not the number one rule of performance. If it did then there would be no reason for SO to exist. Testing and thinking both take time and developer resources. If the theory is clear as to what is faster and does not require much thought to come to that conclusion, then thinking is clearly superior, and testing only useful for verification for those that can't.
@PaulChilds: You are taking a relative statement as absolute and I don't know why. When talking about micro-optimizations your ability to think hard enough to optimize CLR code is suspect. Understanding what performance optimizations the C# compiler, JIT and even your CPU might do is a very complex topic and not worthwhile for simple topics like "is there a performance cost to using properties vs fields". If you are talking about an algorithm then yes do your homework. And in either case any performance improvement that isn't measured is suspect. Which agrees with my point.
Touting something dogmatically as the #1 rule, is by definition an absolute statement
@PaulChilds: Testing is the #1 rule of performance by any reasonable measure. You are emphatically wrong that thought is more important to performance tuning. And what this question is, is emphatically not performance design, the only space where thinking is better.
8

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).

2 Comments

thanks... but please drop the comments on debug. i am not that stupid trying to compare debug builds for performance.. cheers
The are ver valid because when yo uget into more complicated stuff you will find a lot of small thigns behaving different. The GC for examplke deos NOT clean up a lot of stuff fast, too. Keepds references around a lot longer.
7

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.

1 Comment

It's recommended to use BenchmarkDotNet for benchmarks, it will yield more precise results.
5

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.

2 Comments

good point. however its too elaborate. it is easier to launch it from the explorer (especially since VS has Open Folder in File Explorer command ;))
But then if you want to debug and see if something was inlined or not, you'll have to attach the debugger as a separate step. By unchecking these two options you can simply press F5 to debug your optimised build and step in the generated assembly code.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.