Daily Knowledge Drop
The ability to mark a variable as Nullable
(using the ? syntax) is a useful feature in C#, but does come with a large (relative to non-nullable) performance cost.
Today we'll have a look at some simple benchmarks and how making a variable nullable impacts performance.
Benchmark
The benchmarks were be run on .NET6, using BenchmarkDotNet.
[Benchmark(Baseline =true)]
[Arguments(1, 2)]
[Arguments(2, 4)]
[Arguments(0, 0)]
public void NotNullableParameters(int a, int b)
{
int result = 0;
for (int i = 0; i < 1000; i++)
{
result += a * b;
}
}
[Benchmark]
[Arguments(1, 2)]
[Arguments(2, 4)]
[Arguments(0, 0)]
[Arguments(null, null)]
public void NullableParameters(int? a, int? b)
{
int? result = 0;
for (int i = 0; i < 1000; i++)
{
result += a * b;
}
}
The logic of each method is the same, and is straight-forward - multiple two int values, 1000 times, and keep a running total of the result.
The first benchmark will us non-nullable int values, while the second will use nullable int values.
Results
Running the benchmarks yields the following results:
Method | a | b | Mean | Error | StdDev | Ratio | RatioSD |
---|---|---|---|---|---|---|---|
NullableParameters | ? | ? | 1,196.4 ns | 21.79 ns | 20.38 ns | ? | ? |
NotNullableParameters | 0 | 0 | 240.0 ns | 3.53 ns | 3.13 ns | 1.00 | 0.00 |
NullableParameters | 0 | 0 | 1,051.0 ns | 5.70 ns | 4.45 ns | 4.37 | 0.06 |
NotNullableParameters | 1 | 2 | 241.4 ns | 3.74 ns | 3.50 ns | 1.00 | 0.00 |
NullableParameters | 1 | 2 | 1,054.5 ns | 8.60 ns | 7.18 ns | 4.37 | 0.07 |
NotNullableParameters | 2 | 4 | 240.8 ns | 2.92 ns | 2.73 ns | 1.00 | 0.00 |
NullableParameters | 2 | 4 | 1,053.6 ns | 8.73 ns | 6.81 ns | 4.38 | 0.06 |
In all cases, the non-nullable version is approximately 4.4x faster than the nullable version
.
Bear in mind, this is measured in nano-seconds, so probably won't make a material different to performance, unless the logic is computational heavy.
Lowered code
Using sharplab.io to have a look at the lowered code, there a number of additional checks performed with the nullable version, all of which adds to the performance difference.
The non-nullable
version is lowered to the following:
public void NotNullableParameters(int a, int b)
{
int num = 0;
int num2 = 0;
while (num2 < 1000)
{
num += a * b;
num2++;
}
}
While the nullable
version is lowered to this:
public void NullableParameters(Nullable<int> a, Nullable<int> b)
{
Nullable<int> num = 0;
int num2 = 0;
while (num2 < 1000)
{
Nullable<int> num3 = num;
Nullable<int> num4 = a;
Nullable<int> num5 = b;
Nullable<int> num6 = ((num4.HasValue & num5.HasValue) ?
new Nullable<int>(num4.GetValueOrDefault() * num5.GetValueOrDefault()) :
null);
num = ((num3.HasValue & num6.HasValue) ?
new Nullable<int>(num3.GetValueOrDefault() + num6.GetValueOrDefault()) :
null);
num2++;
}
}
Notes
As with most things, there is a trade-off, in this case - the convenience of nullable types vs the performance impact their usage brings.
The performance penalty when using nullable types is measured in nano-seconds, and for the most part won't have any noticeable impact on performance of the application. However if a large number of operations are being performed on nullable types, and performance is to be improved, then one can look at converting the nullable types to non-nullable.
References
What is the cost of Nullable in .NET
Daily Drop 97: 16-06-2022
At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.
The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.On This Page