Switch가 If보다 빠름(C#에서)

C#은 if , else , switch , while , for 등의 여러 제어 구조를 지원합니다. 제어 구조를 사용하면 코드를 기반으로 가능한 여러 경로로 코드를 분할할 수 있습니다.



제어 구조의 일반적인 그래픽 표현
ifswitch 문은 오늘날 대부분의 프로그래밍 언어에서 매우 잘 알려져 있으며 채택되었습니다. 또한 종종 동일한 사용 사례에 적용됩니다.

실험


Jan , Feb , Mar , ... 와 같이 월 이름의 세 자리 문자열이 주어지면 해당하는 월 번호를 정수로 가져오고 싶습니다. 결과는 Jan = 1 , Feb = 2 , Mar = 3 등입니다.

if 구현



이것은 ifreturn 를 사용한 기본 구현입니다.

private int GetMonthIndexIf(string month)
{
    if (month == "Jan")
    {
        return 1;
    }

    if (month == "Feb")
    {
        return 2;
    }

    if (month == "Mar")
    {
        return 3;
    }
    // and so on
}


스위치 구현



이는 switch 문을 사용한 동등한 구현입니다.

private int GetMonthIndexSwitch(string month)
{
    return month switch
    {
        "Jan" => 1,
        "Feb" => 2,
        "Mar" => 3,
        "Apr" => 4,
        "May" => 5,
        "Jun" => 6,
        "Jul" => 7,
        "Aug" => 8,
        "Sep" => 9,
        "Okt" => 10,
        "Nov" => 11,
        "Dez" => 12,
        _     => throw new ArgumentException(),
    };
}


The syntax used above is called a switch expression.
This is available since C# 8.0, see: MSDN Docs



사전 구현



참고로 Dictionary<string, int> 를 기반으로 한 구현도 만들었습니다.

private int GetMonthIndexDictionary(string month)
{
    // _monthsDict maps the month name to the index
    return _monthsDict[month];
}


벤치마크 설정



벤치마크를 실행하기 위해 BenchmarkDotNet를 사용하고 있습니다. 모든 GetMonthIndex* 메서드에 대해 Jan에서 Dez까지의 모든 월이 N번 호출되어 이러한 메서드의 모든 경로를 포함합니다. 벤치마크는 N=1, 10, 100 에 대해 세 번 실행됩니다.

벤치마크 결과




BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19044.1645 (21H2)
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.104
  [Host]     : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT
  DefaultJob : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT


|                   Method |   N |        Mean |     Error |    StdDev |
|------------------------- |---- |------------:|----------:|----------:|
|     GetMonthIndex_Switch |   1 |    173.7 ns |   0.77 ns |   0.72 ns |
|         GetMonthIndex_If |   1 |    485.6 ns |   3.13 ns |   2.78 ns |
| GetMonthIndex_Dictionary |   1 |    325.8 ns |   1.70 ns |   1.42 ns |
|     GetMonthIndex_Switch |  10 |    960.9 ns |   5.56 ns |   5.20 ns |
|         GetMonthIndex_If |  10 |  2,528.3 ns |  15.35 ns |  14.36 ns |
| GetMonthIndex_Dictionary |  10 |  1,793.8 ns |  25.68 ns |  22.77 ns |
|     GetMonthIndex_Switch | 100 |  8,211.9 ns |  68.39 ns |  53.40 ns |
|         GetMonthIndex_If | 100 | 26,460.3 ns | 513.94 ns | 990.18 ns |
| GetMonthIndex_Dictionary | 100 | 17,662.1 ns | 286.67 ns | 254.13 ns |


위와 같이 GetMonthIndex_Switch가 가장 빠르고,GetMonthIndex_Dictionary가 2위이고 GetMonthIndex_If가 가장 느립니다.

Mind that these are nanoseconds (!) it won't effect your application performance very much.



이제 switchif보다 빠른 이유는 무엇입니까?

switch 문이 증류되었습니다.



사실 C#에는 switch 문이 없습니다. C# 컴파일에는 switch 문을 if 문으로 다시 작성하는 단계가 있습니다. 이 단계를 낮추기라고 합니다. 고급 언어 기능을 저급 언어 기능으로 변환합니다.

You can read more about it here.
You will also notice that this is very widely used.



sharplab.io을 사용하면 낮추는 동안 생성되는 C# 코드를 볼 수 있습니다. 원본GetMonthIndexSwitch을 거기에 붙여 넣으면 if 문으로 다시 작성된 것을 볼 수 있습니다.

private int GetMonthIndexSwitch(string month)
{
    uint num = <PrivateImplementationDetails>.ComputeStringHash(month);
    if (num <= 1118301483)
    {
        if (num <= 749839599)
        {
            if (num != 566134113)
            {
                if (num != 663571330)
                {
                    if (num == 749839599 && month == "Sep")
                    {
                        return 9;
                    }
                }
                else if (month == "Feb")
                {
                    return 2;
                }
            }
            else if (month == "Okt")
            {
                return 10;
            }
        }
        else if (num != 1000858150)
        {
            if (num != 1046388392)
            {
                if (num == 1118301483 && month == "Mar")
                {
                    return 3;
                }
            }
            else if (month == "Dez")
            {
                return 12;
            }
        }
        else if (month == "May")
        {
            return 5;
        }
    }
    else if (num <= 1190317742)
    {
        if (num != 1153511100)
        {
            if (num != 1187066338)
            {
                if (num == 1190317742 && month == "Jan")
                {
                    return 1;
                }
            }
            else if (month == "Jun")
            {
                return 6;
            }
        }
        else if (month == "Jul")
        {
            return 7;
        }
    }
    else if (num != 2213879282u)
    {
        if (num != 2319303684u)
        {
            if (num == 2699988948u && month == "Aug")
            {
                return 8;
            }
        }
        else if (month == "Nov")
        {
            return 11;
        }
    }
    else if (month == "Apr")
    {
        return 4;
    }
    throw new ArgumentException();
}


if 문을 사용하는 솔루션과 비교할 때 동일한 출력을 생성하지만 완전히 다른 방식으로 작동합니다.

모든 경우를 거치는 대신 트리와 같은 구조를 구축합니다. 여기에서 성능 향상이 발생합니다. 가능한 모든 사례를 반복할 때 일치 항목을 찾는 것보다 트리 구조에서 일치 항목을 찾는 것이 더 빠릅니다. 아래에서 트리 구조의 예를 볼 수 있습니다.

The compiler only rewrites it into a tree-structure if there are enough possible cases. When there are less than seven cases, no tree-structure is built.





예제 트리 구조. 위의 낮추는 동안 생성된 코드와 일치하지 않습니다.

요약


  • Switch 문은 컴파일러에 의해 if 문으로 다시 작성됩니다.
    트리 구조로 다시 작성하기 때문에 사례가 많을수록 더 빨라집니다.
  • 다른 많은 고급 언어 기능이 있습니다.
    컴파일의 낮추는 단계에서 다시 작성됩니다.
  • 사례가 12개인 이 시나리오에서 if가 가장 느리고 dictionary가 두 번째입니다.switch가 가장 빠릅니다.
    그럼에도 불구하고 응용 프로그램의 성능에는 영향을 미치지 않을 것입니다.
  • 좋은 웹페이지 즐겨찾기