Which is faster, Nullable<T>.Value
or Nullable<T>.GetValueOrDefault()
?
Before I answer that question, my standard response to “which horse is faster?” questions applies. Read that first.
.
.
.
Welcome back. But again, before I answer the question I need to point out that the potential performance difference between these two mechanisms for obtaining the non-nullable value of a nullable value type is a consequence of the fact that these two mechanisms are not semantically equivalent. The former may legally only be called if you are sure that the nullable value is non-null; put another way, calling Value
without knowing that HasValue
is true is a boneheaded exception. The latter may be called on any nullable value. A glance at a simplified version of the source code illustrates the difference.
struct Nullable<T> where T : struct
{
private bool hasValue;
private T value;
public Nullable(T value)
{
this.hasValue = true;
this.value = value;
}
public bool HasValue { get { return this.hasValue; } }
public T Value
{
get
{
if (!this.HasValue) throw something;
return this.value;
}
}
public T GetValueOrDefault()
{
return this.value;
}
... and then all the other conversion gear and so on ...
}
The first thing to notice is that a nullable value type’s ability to represent a “null” integer or decimal or whatever is not magical. (Nullable value types are magical in other ways; for example, there’s no way to write your own struct that has the strange boxing behaviour of a nullable value type; an int? boxes to either an int or null, never to a boxed int?. But let’s not worry about these magical features today.) A nullable value type is nothing more than an instance of the value type plus a bool
saying whether it’s null or not.
If a variable of nullable value type is initialized with the default constructor then the hasValue
field will be its default value, false
, and the value
field will be default(T)
. If it is initialized with the declared constructor then of course the hasValue
field is true and the value
field is any legal value, including possibly T
‘s default value. Thus, the implementation of GetValueOrDefault()
need not check the flag; if the flag is true then the value
field is set correctly, and if it is false, then it is set to the default value of T
.
Looking at the code it should be clear that Value
is almost certainly not faster than GetValueOrDefault()
because obviously the former does exactly the same work as the latter in the success case, plus the additional work of the flag check. Moreover, because GetValueOrDefault()
is so brain-dead simple, the jitter is highly likely to perform an inlining optimization.
An inlining optimization is where the jitter eliminates an unnecessary “call” and “return” instruction by simply generating the code of the method body “inline” in the caller. This is a great optimization because doing so can make code both smaller and faster in some cases, though it does make it harder to debug because the debugger has no good way to generate breakpoints inside the inlined method.
How the jitter chooses to inline or not is an implementation detail, but it is reasonable to assume that it is less likely to perform an inlining optimization on code that contains more than one “basic block” and has a throw in it.
A “basic block” is a region of code where you know that the code will execute from the top of the block to the bottom without any “normal” branches in or out of the middle of the block. (A basic block may of course have exceptions thrown out of it.) Many optimizing compilers use “basic blocks” as an abstraction because it abstracts away the unnecessary details of what the block actually does, and treats it solely as a node in a flow control graph.
It should also be clear that though the relative performance difference might be large, the absolute difference is small. A call, field fetch, conditional jump and return in the typical case makes up the difference, and those things are each only nanoseconds.
Now, this is of course not to say that you should willy-nilly change all your calls to Value
to GetValueOrDefault()
for performance reasons. Read my rant again if you have the urge to do that! Don’t go changing working, debugged, tested code in order to obtain a performance benefit that is (1) highly unlikely to be a real bottleneck, and (2) highly unlikely to be your worst performance problem.
And besides, using Value
has the nice property that if you have made a mistake and fetched the value of a null, you’ll get an exception that informs you of where your bug is! Code that draws attention to its faults is a good thing.
Finally, I note that here we have one of those rare cases where the frameworks design guidelines have been deliberately bent. We have a “Get” method is actually faster than a property getter, and the property getter throws! Normally you expect the opposite: the “Get” method is usually the one that is slow and can throw, and the property is the one that is fast and never throws. Though this is somewhat unfortunate, remember, the design guidelines are our servants, not our masters, and they are guidelines, not rules.
Next time on FAIC: How does the C# compiler use its knowledge of the facts discussed today to your advantage? Have a great Christmas everyone; we’ll pick up this subject again in a week.