15

I was optimizing my code, and I noticed that using properties (even auto properties) has a profound impact on the execution time. See the example below:

[Test]
public void GetterVsField()
{
    PropertyTest propertyTest = new PropertyTest();
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    propertyTest.LoopUsingCopy();
    Console.WriteLine("Using copy: " + stopwatch.ElapsedMilliseconds / 1000.0);

    stopwatch.Restart();
    propertyTest.LoopUsingGetter();
    Console.WriteLine("Using getter: " + stopwatch.ElapsedMilliseconds / 1000.0);
    stopwatch.Restart();
    propertyTest.LoopUsingField();
    Console.WriteLine("Using field: " + stopwatch.ElapsedMilliseconds / 1000.0);
}

public class PropertyTest
{
    public PropertyTest()
    {
        NumRepet = 100000000;
        _numRepet = NumRepet;
    }

    int NumRepet { get; set; }
    private int _numRepet;
    public int LoopUsingGetter()
    {
        int dummy = 314;
        for (int i = 0; i < NumRepet; i++)
        {
            dummy++;
        }
        return dummy;
    }

    public int LoopUsingCopy()
    {
        int numRepetCopy = NumRepet;
        int dummy = 314;
        for (int i = 0; i < numRepetCopy; i++)
        {
            dummy++;
        }
        return dummy;
    }

    public int LoopUsingField()
    {
        int dummy = 314;
        for (int i = 0; i < _numRepet; i++)
        {
            dummy++;
        }
        return dummy;
    }
}

In Release mode on my machine I get:

Using copy: 0.029
Using getter: 0.054
Using field: 0.026 

which in my case is a disaster - the most critical loop just can't use any properties if I want to get maximum performance.

What am I doing wrong here? I was thinking that these would be inlined by the JIT optimizer.

ritesh
  • 907
  • 3
  • 11
  • 31
Grzenio
  • 35,875
  • 47
  • 158
  • 240
  • 9
    When you say "in release mode" do you mean a release *build* configuration, or running without the debugger? If you're running in the debugger, I'd fully expect to see a significant hit. Also note that it's pretty unusual for a loop to be *this* tight... and reasonable to micro-optimize *just* those parts of your application which prove to be bottlenecks. – Jon Skeet Mar 07 '14 at 09:44
  • 8
    I've just tested the code myself, and the x86 JIT makes the property access basically the same as the field access. The x64 JIT shows the behaviour you've put in the question. You might want to try with the new x64 JIT which is coming out soon: http://blogs.msdn.com/b/dotnet/archive/2014/02/27/ryujit-ctp2-getting-ready-for-prime-time.aspx – Jon Skeet Mar 07 '14 at 09:47
  • @JonSkeet, I mean release build configuration. I was running these tests from ReSharper test runner to be precise. – Grzenio Mar 07 '14 at 10:34
  • 2
    Right - I would try to do performance tests in a standalone environment, with as little as possible to interfere with the results, personally. (I like using a simple console app to do this.) However, as I say it looks like it's the x64 JIT not being as smart at inlining as the x86 JIT. – Jon Skeet Mar 07 '14 at 10:35
  • 13
    While it doesn't explain the difference: I don't see how this has "profound impact" or is a "disaster". You're executing your piece of code a **billion times** and still, **in the worst case it only requires 54 ms**. If you want more performances, use C or ASM instead of .Net. – ken2k Mar 07 '14 at 10:36
  • 1
    @ken2k, this is just an isolated example. Increase the number of repetitions if you want to wait longer for the results. The real code runs on a grid of thousands of machines and takes about 3h. Introducing a property in a refactoring in the innermost loop caused an increase of 10% of the execution time. Now we know we should have been using `C++` like everyone else for scientific computing :) – Grzenio Mar 07 '14 at 10:53
  • @Grzenio While I'm definitely a big fan of .Net (I almost always recommend C#/.Net), yes, if performances are _that_ critical, use C/C++/ASM instead. Right now .Net hasn't deterministic execution per definition (mostly because of the GC) and JIT compilation while great hasn't as much performances as very low-level (well written) code. – ken2k Mar 07 '14 at 11:12
  • Properties have some advantage logic in get; set; blocks not only returning a value like a simple variable. It makes properties slower than variables. When you have an object and its public properties it looks good but if you have an object and its public fields it looks ugly. Your choise. .Net oriented on "beautiful, understandable code", not on speed. if you need performance compile libraries under C and use .Net only for user interface for an instance. – Wallstrider Mar 07 '14 at 12:15
  • @ken2k 0.054 instead of 0.026 still is a disaster. It takes twice as long which is indeed bad. We don't always get to pick the language of solution, which renders "pick language x for performance" useless really. – Dbl Mar 07 '14 at 13:28
  • 2
    http://stackoverflow.com/questions/632831/why-are-public-fields-faster-than-properties – blfuentes Mar 07 '14 at 13:30
  • 3
    @AndreasMüller No it's not a disaster _at all_ IMO. If this small performance issue _is_ a real problem, then I'm sorry but there will be _tons_ of other performance issues similar or **way worse** _because of_ .Net. The first obvious example is the **garbage collector execution** that will be a **real** disaster, not just a few nanoseconds loss per execution. – ken2k Mar 07 '14 at 13:37
  • 1
    @Grzenio I'm sure if you will read my answer carefully you find out why your code is not jitted. The problem is that the getter returns value type and 64bit Jit compiler will not in-line it – Aik Mar 17 '14 at 10:49
  • Have you tried launching the test from command line as this [link](http://stackoverflow.com/questions/9842917/field-vs-property-optimisation-of-performance) suggest – j-p Apr 12 '14 at 16:39

5 Answers5

1

Getters/Setters are syntactic sugar for methods with a few special conventions ("value" variable in a setter", and no visible parameter list).

According to this article, "If any of the method's formal arguments are structs, the method will not be inlined." -- ints are structs. Therefore, I think this limitation applies.

I haven't looked at the IL produced by the following code, but I did get some interesting results that I think shows this working this way...

using System;
using System.Diagnostics;

public static class Program{
public static void Main()
{
    PropertyTest propertyTest = new PropertyTest();
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    propertyTest.LoopUsingField();
    Console.WriteLine("Using field: " + stopwatch.ElapsedMilliseconds / 1000.0);


    stopwatch.Restart();
    propertyTest.LoopUsingBoxedGetter();
    Console.WriteLine("Using boxed getter: " + stopwatch.ElapsedMilliseconds / 1000.0);

    stopwatch.Restart();
    propertyTest.LoopUsingUnboxedGetter();
    Console.WriteLine("Using unboxed getter: " + stopwatch.ElapsedMilliseconds / 1000.0);

}

}
public class PropertyTest
{
    public PropertyTest()
    {
        _numRepeat = 1000000000L;
        _field = 1;
        Property = 1;
        IntProperty = 1;
    }

    private long _numRepeat;
    private object _field = null;
    private object Property {get;set;}
    private int IntProperty {get;set;}

    public void LoopUsingBoxedGetter()
    {

        for (long i = 0; i < _numRepeat; i++)
        {
          var f = Property;
        }

    }

    public void LoopUsingUnboxedGetter()
    {
        for (long i = 0; i < _numRepeat; i++)
        {
            var f = IntProperty;
        }
    }

    public void LoopUsingField()
    {
        for (long i = 0; i < _numRepeat; i++)
        {
            var f = _field;
        }
    }
}

This produces.. ON MY MACHINE, OS X (recent version of Mono), these results (in seconds):

  • Using field: 2.606
  • Using boxed getter: 2.585
  • Using unboxed getter: 2.71
Andrew Theken
  • 3,392
  • 1
  • 31
  • 54
  • On Windows (.net v4.5, x64), I did not get meaningful, or reproducible differences in timings. Changing the order of the benchmarks can also impact results, due to JIT overhead (especially on "small" runs that last only 50ms.) – Andrew Theken Mar 17 '14 at 20:43
0

You say you are optimizing your code, but I am curious as to how, what the functionality is supposed to be, and what the source data coming into this is as well as it's size as this is clearly not "real" code. If you are parsing a large list of data in consider utilizing the BinarySearch functionality. This is significantly faster than, say the .Contains() function with very large sets of data.

List<int> myList = GetOrderedList();
if (myList.BinarySearch(someValue) < 0)
// List does not contain data

Perhaps you are simply looping through data. If you are looping through data and returning a value perhaps you may want to utilize the yield keyword. Additionally consider the potential use of the parallel library if you can, or utilize your own thread management.

This does not seem like what you want judging by the posted source but it was very generic so I figured this was worth mentioning.

public IEnumerable<int> LoopUsingGetter()
{
    int dummy = 314;

    for (int i = 0; i < NumRepet; i++)
    {
        dummy++;
        yield return dummy;
    }
}

[ThreadStatic]
private static int dummy = 314;

public static int Dummy
{
    get
    {
        if (dummy != 314) // or whatever your condition
        {
            return dummy;
        }

        Parallel.ForEach (LoopUsingGetter(), (i)
        {
            //DoWork(), not ideal for given example, but due to the generic context this may help
            dummy += i;
        });
    }

    return dummy;
}
Anthony Mason
  • 165
  • 1
  • 12
  • In our case paralellization is done on a higher level, so the code I was optimizing should remain single threaded. Thanks for a valid point though! – Grzenio Mar 14 '14 at 16:11
0

Follow the 80/20 performance rule instead of micro-optimizing. Write code for maintainability, instead of performance. Perhaps Assembly language is the fastest but that does not mean we should use Assembly language for all purposes.

You are running the loop 100 million times and the difference is 0.02 millisecond or 20 microseconds. Calling a function will have some overhead but in most cases it does not matter. You can trust the compiler to inline or do advanced things.

Directly accessing the field will be problematic in 99% of the cases as you will not have control of where all your variables are referenced and fixing at too many places when you find something is wrong.

Arun Thirupathi
  • 351
  • 2
  • 11
-1

You have to check if optimize code checkbox is checked.

  1. If it is not checked, access to the property is still method call
  2. If it is checked the property is in-lined and the performance is the same as with direct field access because the JITed code will be the same

There is more restriction about inlinig in X64 JIT compiler. More information about JIT64 inlining optimization is there: David Broman's CLR Profiling API Blog: Tail call JIT conditions.

please see point #3 The caller or callee return a value type. If your property will return reference type, the property getter will be in-lined. It means that the property int NumRepet { get; set; } is not inlined but object NumRepet { get; set; } will be inlined if you don't break another restriction.

The optimization of X64 JIT is poor and this is why new one will be introduced as John mention

valiano
  • 16,433
  • 7
  • 64
  • 79
Aik
  • 3,528
  • 3
  • 19
  • 20
  • 1
    Not true. "Optimize code" is checked by default for Release builds, and the performance issue is present even with optimized code. – ken2k Mar 07 '14 at 13:04
  • "If it is checked the property is in-lined and the performance is the same" is still not true, you can test it with the current x64 JIT compiler. – ken2k Mar 07 '14 at 13:11
  • It is desired behaviour and it does if you pass condition from the linked blog. If you change int to object, the property will be inlined even on x64 JIT – Aik Mar 07 '14 at 13:15
  • Because release mode was explicitly mentioned, I cannot help but assume "Optimize Code" is set. If there is a performance issue in debug and release there is a clear issue within the code base or database. – Anthony Mason Mar 14 '14 at 15:02
-1

You should stop the stop watch when it completes the loop, your stopwatch is still running when you are writing to console this can add additional time that can skew your results.

[Test]
public void GetterVsField()
{
    PropertyTest propertyTest = new PropertyTest();
    Stopwatch stopwatch = new Stopwatch();

    stopwatch.Start();
    propertyTest.LoopUsingCopy();
    stopwatch.Stop();
    Console.WriteLine("Using copy: " + stopwatch.ElapsedMilliseconds / 1000.0);

    stopwatch.Reset();
    stopwatch.Start();
    propertyTest.LoopUsingGetter();
    stopwatch.Stop();
    Console.WriteLine("Using getter: " + stopwatch.ElapsedMilliseconds / 1000.0);

    stopwatch.Reset();
    stopwatch.Start();
    propertyTest.LoopUsingField();
    stopwatch.Stop();
    Console.WriteLine("Using field: " + stopwatch.ElapsedMilliseconds / 1000.0);
}
Marko
  • 2,734
  • 24
  • 24
  • The parameters to the method are evaluated before the method runs. Evaluating that property to its value is one of the first things performed on that line of code. Stopping the stopwatch may well even take more time. On top of that, the difference between the two solutions will be effectively zero, even if it would be slower, it would be consistently so. – Servy Mar 17 '14 at 20:54