I’m conducting a test about ref structs and text-ValueType’s
and have step into this doubt while working in the struct constructors.
Apparently, a switch statement controlling a ref variable with an i-style loop can be
faster than manually setting the array positions in the struct’s field.
Im not sure about he validity of this kind of test in the top-level statement method but
here is the code
using System.Diagnostics;
const string testText = $"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Quisque orci purus, vulputate vulputate
""";
const int load_Steps = 250;
CharblockExample bySetBlock ;
CharblockExample bySwitchBlock;
var watcher = Stopwatch.StartNew();
var results = new List<(TimeSpan tic1, TimeSpan tic2)>();
try
{
for (int i = 0; i < load_Steps; i++)
{
var start_tics = watcher.Elapsed;
bySwitchBlock = new(testText.AsSpan(), true);
var ticBlock2 = watcher.Elapsed - start_tics;
start_tics = watcher.Elapsed;
bySetBlock = new(testText.AsSpan(), false);
var ticBlock1 = watcher.Elapsed - start_tics;
Console.WriteLine($"{nameof(bySetBlock)} and {nameof(bySwitchBlock)}: {ticBlock1} vs {ticBlock2}");
results.Add((ticBlock1, ticBlock2));
}
}
finally
{
watcher.Stop();
Console.WriteLine();
var overall_1 = results.Sum(element => element.tic1.Microseconds);
var overall_2 = results.Sum(selector => selector.tic2.Microseconds);
var overall_result = overall_1 - overall_2;
Console.WriteLine($"overall analysis: {(overall_1 is < 0
? $"{nameof(bySetBlock)} was {Math.Abs(overall_result)} ms faster than {nameof(bySwitchBlock)}"
: $"{nameof(bySwitchBlock)} was {Math.Abs(overall_result)} ms faster than {nameof(bySetBlock)}"
)}");
}
public readonly ref struct CharblockExample
{
private readonly int inputtedLength;
private readonly bool _input = false;
private readonly char //char spots
#region Char-Spots
_0, _1, _2, _3, _4, _5,
_6, _7, _8, _9, _10;
#endregion
public CharblockExample(ReadOnlySpan<char> chars, bool performTest)
{
_input = true;
Span<char> arr = stackalloc char[MAX_LENGTH];
inputtedLength = chars.Length;
if (chars.Length > MAX_LENGTH)
throw new ArgumentOutOfRangeException(nameof(chars), $"""
The provided char array is longer than the max of 256 chars for this block.
""");
for (int i = 0; i < MAX_LENGTH; i++)
{
if (i >= chars.Length) break;
arr[i] = chars[i];
}
if (performTest)
{
int pos = 0;
ref var _ref = ref _1;
while (pos < MAX_LENGTH - 1)
{
switch (pos)
{
case 0: _ref = ref _0; break;
case 1: _ref = ref _1; break;
case 2: _ref = ref _2; break;
case 3: _ref = ref _3; break;
case 4: _ref = ref _4; break;
case 5: _ref = ref _5; break;
case 6: _ref = ref _6; break;
case 7: _ref = ref _7; break;
case 8: _ref = ref _8; break;
case 9: _ref = ref _9; break;
case 10: _ref = ref _10; break;
}
pos++;
_ref = arr[pos];
}
}
else
{
_0 = arr[0]; _1 = arr[1]; _2 = arr[2]; _3 = arr[3]; _4 = arr[4];
_5 = arr[5]; _6 = arr[6]; _7 = arr[7]; _8 = arr[8]; _9 = arr[9];
_10 = arr[10];
}
}
public char[] CharArr => new[]
{
_0, _1, _2, _3, _4, _5,
_6, _7, _8, _9, _10
};
private const int MAX_DISPLAYABLE = 15;
private const int MAX_LENGTH = 256;
}
that’s my result after the full execution:
overall analysis: bySwitchBlock was 1240 ms faster than bySetBlock
- In my judgement, only setting without checking for any boolean logic or advancing a integer indexer should be faster, its just in-line execution of commands for what i see
>Solution :
Testing the CharblockExample from the first revision of your question using BenchmarkDotNet gives the result that using a switch is about 2 times slower than not using it.
BenchmarkDotNet v0.13.7, Windows 10 (10.0.19045.3086/22H2/2022Update)
Intel Core i7-6700 CPU 3.40GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET SDK 7.0.304
[Host] : .NET 7.0.7 (7.0.723.27404), X64 RyuJIT AVX2
DefaultJob : .NET 7.0.7 (7.0.723.27404), X64 RyuJIT AVX2
Method Mean Error StdDev Switch 363.3 ns 7.18 ns 11.18 ns NoSwitch 170.2 ns 3.44 ns 3.96 ns
This is to be expected. You are doing a lot of extra work there – controlling the loop, jumping around with a jump table, etc. I also wouldn’t be surprised if the jitter can recognise the pattern and optimise the series of assignments in the switch-less code into something a lot faster.
Here is benchmark code:
public class Benchmark
{
const string testText = $"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Quisque orci purus, vulputate vulputate mattis quis,
accumsan in turpis. Vestibulum quis sapien iaculis,
pulvinar magna sed, lacinia lectus. Donec nec felis nec
metus dictum aliquet vel non.
""";
[Benchmark]
public CharblockExample Switch() => new CharblockExample(testText, true);
[Benchmark]
public CharblockExample NoSwitch() => new CharblockExample(testText, false);
}
public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<Benchmark>();
}
}
Generally, microbenchmarking code like this would produce rather inaccurate results. Lots of external factors can affect the final result. You should use a