Performance Guidelines for Properties


I can’t say I’ve asked the framework guidelines folks about this but I’m fairly sure there would be a lot of agreement from the guidelines gurus; so in the spirit of approximately correct advice I give you Rico’s Guidelines for Performant Properties.

I should start by saying I wish more people just used fields instead of committing these terrible sins with their properties.  If we had the notion of a type-safe, read-only field the world would be a better place.  Alas…

So, you’re using a property, the most important thing to remember is that it will seem very much like a field in all ways.  It looks like a field and feels like a field, people will expect it to perform like a field.  So with that in mind:

  • Accessing the property should not allocate any memory; people are going to use this thing in loops walking over whatever data structure you are presenting, they expect that this data is sitting around and being accessed.
  • Accessing the property should not “synchronize” – if there is any locking to be done it should be done at a higher level than a single field, by the time you’ve acquired whatever object has the property on it, it should already be safe to read it.
  • Accessing the property should not do any I/O, especially not any network I/O, again see above, by the time you’re reading the property the object offering it should have done whatever I/O was needed.
  • Accessing the property should not be an operation with complexity greater than O(1) – that means no loops.  At all.  You could haggle me as high as O(lg(N)) if we’re talking about an property that is an “indexer” but no higher.
  • Accessing the property should not have side-effects (i.e. it’s strictly a read operation, it changes nothing)

What you’re left with is you can use your object state and the argument (if an indexer) to do a constant-time lookup, or log-time at worst, in an already existing data structure and immediately return the result.  That’s it.

Why?

Pit of Success is the only reason you need.  In my opinion, patterns more complicated than the above are doomed to fail.  If you allow say network access, an RPC or the like, on each property fetch, then you promote things like field-at-a-time access to remote data.  Not only is this astonishing to users of the API it is incredibly inefficient.  If you follow the rules above you soon realize that you must allow some kind of query to get the data you need and then you can use properties all you like to access that data.  That is a much better pattern, it leads to success.

Remember that if you allow things like I/O or synchronization you can easily get property access times that are measured in milliseconds, this simply will not do.  A typical interactive scenario might have a budget of say 100ms for prompt response and it might require access to several dozen properties just to paint the screen properly.  This has several issues:

  • If property access is say 1 millisecond then accessing 10 properties (not at all crazy) would be 10% of your overall budget – that’s nuts!
  • The interactive code must be able to use your properties with confidence, that means they cannot block and should not be subject to large variability, else you get UI that is non-responsive for extended periods of time

Frequent access to properties is very common and therefore must be astonishingly cheap if only to keep a handle on the Joules your program consumes – an increasingly important metric for modern platforms.

From a time perspective, I like to see properties that have access time measured in nanoseconds.  Let’s say 10^-8s.  I can reasonably see systems with tricky indexing that might go has high as 10^-7 or even 10^-6.  When your time is getting to be 10^-5 or larger, you really need to start thinking about your design.  That’s too much work to put inside a thing that looks like a field access.  Give your users a fighting chance to understand where their costs are and let them query for a batch of results, probably asynchronously and then access them quickly when they arrive.  That’s going to be a successful pattern.

It goes without saying that under these conditions, the flexibility to version/replace/subtype virtual-call properties is actually illusory.  If you were to try to do something new and costly, or even just very different in a subtype you would likely cause all manner of problems for callers.  So while you do get to change things a somewhat, you cannot and must not, use that flexibility to access new and different subsystems or the like inside of a method that is supposed to be as small as a getter.  You are necessarily very constrained.

When I consider all of this I arrive squarely at the conclusion that more fields and fewer properties would be better.  Don’t hide real work inside a property and don’t use them as a synchronization tool.  Once you decide that you need a fetch/read pattern you soon find that that reading thing can be very simple/fast indeed and that perhaps fields were just fine after all.

Something to think about anyway. 

Comments (12)

  1. Daniel Neely says:

    Does lazy initialization play any part in your consideration of what's acceptable?  The alternate pattern for a property exposing a value that's either large or not particularly fast to initialize isn't appealing either.

    BigObject  LargeInfrequentlyUsedValue

    {

      get

      {

           if (_largeInfrequentlyUsedValue == null)

               throw new Exception("You need to call InitializeInfrequentlyUsedValue() ;

           else return _infrequentlyUsedValue;

       }

    }

    I do agree with your overall point, and wish more people were aware of it.  The largest performance boost I've done to code I was responsible for maintaining in the last few years was a 3-50x gain (for typical data set sizes) by replacing an indexer using a linear search with an O(1) calculation of the correct location in the backing List<>.

  2. Björn says:

    Well, one alternative would be to change it to a function. GetLargeInfrequentlyUsedValue()

    That way its more clear that its not a simple property

  3. ricom says:

    I just added the bit about properties not causing side-effects, I'd meant to include that and forgotten.

    Properties that might be fast or might not be fast, that might fail or might not fail, are a major source of problems.

    @Daniel: The exact example that you give above has no allocations on the non-exceptional path and a clean failure path so I wouldn't worry about it.  But really simpler is better.  And as Bjorn says, just changing it to a function is enough to make it clear that there is more there than meets the eye.

    Remember, "The code is more what you'd call 'guidelines' than actual rules…"

  4. Henry Boehlert says:

    Even if I follow your recommendation (prefer field/hashtable-backed properties), it still will be hard for the compiler to reason whether my property value can change between two calls to get_Property and more often than not it won't be able to optimize away the second call.

    It is still a pity .NET (or C#/VB, fwiw) don't support const declarations in the C++ sense, or at least real read-only properties. That would give the compiler at least some hint and could even provide support for virtual properties.

  5. tobi says:

    All of this article is very true and best practice. In the performance hot-spots for performance reasons, in the cold-spots for maintenance reasons. Although the rules in the latter case are relaxed.

  6. Martin Jul says:

    From a performance point you are spot on, but it is also relevant to look at it from a design point of view. Properties (and fields) tend to expose implementation details, leading to tightly coupled code, as argued in the classic article by Allen Holub, “Why getter and setter methods are evil”

    (you have to forgive the Java folks that they did not have properties back when he wrote that):

    http://www.javaworld.com/…/jw-0905-toolbox.html

    "Though getter/setter methods are commonplace in Java, they are not particularly object oriented (OO). In fact, they can damage your code's maintainability. Moreover, the presence of numerous getter and setter methods is a red flag that the program isn't necessarily well designed from an OO perspective. "

    His point is that properties tend to give the illusion of encapsulation, while actually exposing implementation. Fields are more honest about exposing zero-behaviour implementation details. The alternative is using double-dispatch, interfaces etc. but most people consider that a lot of work in languages like C# and tend to prefer properties for that reason.

    I think a good rule of thumb for using fields/properties is to at least encapsulate the value they give out, e.g.

    Don't give a Price field the type "double" just because it would currently fit in that data type. Create a Price or Money struct instead and expose that. Then it's easier to change the implementation when you realise that doubles have rounding errors that are not acceptable. From a perf view this should be just as good, from a design view the semantics of the field are much clearer, and from a maintainability view it saves you a lot of trouble.

    In any case remember, it’s all about trade-offs, not absolutes.

  7. Daniel Neely says:

    I think I wasn't clear enough in my first comment.  Throwing an exception for a lazy initialized property eliminates the potential performance surprise from first access that would occur if the null check called the initializer, but does so at the cost of breaking the encapsulation of the lazy initialization because the caller needs to explicitly call the initialization method.  I'm not sure that tradeoff is worthwhile since you're making things harder on the caller; and if a future version is able to replace the heavy initialization with something light enough not to need special behavior you're forced to modify all the using code to make the change.

  8. ricom says:

    @Daniel: indeed I like it much better than the auto-init flavor

    @Martin: of course you're right: it's all about the tradeoffs but on the other hand I do like to prime the pump if you will with what greatness looks like.  At least to my eyes :)    Thanks for the reference, a lot of truth there too.

  9. Chris Marisic says:

    What about properties that behave as such?

    public IList<Foo> Bars { get { return Bars ?? (Bars = new List<Food>()) } set { _bars = value }}

    I actually hate having to define these setters and targeting a backing field. Seems like something the auto property should be able to handle. or a keyword like _field = value etc.

  10. ricom says:

    @Chris:  This is a strange pattern, the class in question is accepting a list of Bars from anyone at all and you're willing to lazy-init an empty list in the event that you don't have one yet.  Normally I'd say you should be careful not to expose your guts like that but in this case we're clearly talking about some kind of argument acceptor/builder.

    The lazy init stuff often ends up being more trouble than its worth.  Consider:

    if (yourThing.Bars.Count == 0) {

    }

    Now we made an empty list just so we could see that it's empty?

    Following the no-alloc rule if you have a read-only empty ilist that can't be changed and you returned that then you get a much better pattern.

    _bars = mySharedReadonlyEmptyList;

    public IList<Foo> Bars { get { return _bars; } set { _bars = value }}

    Life is good.  Count is free.  Bars is simple.  10/10.

    But that whole pattern is scary… you can't assume anything about Bars from call to call becuase they can change the guts of the list and you would never know.

    This is really a whole different kind of sin.

  11. Great to see your blog being active again.

    When shall I expect to see a series of "History of Internet Explorer"? :)

  12. ricom says:

    Umm… in 21 years? :)