Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Switch statement over set statement

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

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading