developer tip

더 빠른 것은 무엇입니까, 문자열 또는 elseif on type?

optionbox 2020. 10. 18. 09:20
반응형

더 빠른 것은 무엇입니까, 문자열 또는 elseif on type?


문자열 비교를 기반으로하거나 유형을 비교하여 사용할 코드 경로를 식별 할 수있는 옵션이 있다고 가정 해 보겠습니다.

어느 것이 더 빠르고 그 이유는 무엇입니까?

switch(childNode.Name)
{
    case "Bob":
      break;
    case "Jill":
      break;
    case "Marko":
      break;
}

if(childNode is Bob)
{
}
elseif(childNode is Jill)
{
}
else if(childNode is Marko)
{
}

업데이트 : 내가 이것을 묻는 주된 이유는 switch 문이 케이스로 간주되는 것에 대해 독특하기 때문입니다. 예를 들어 변수를 사용할 수 없으며 주 어셈블리로 이동하는 상수 만 사용할 수 있습니다. 나는 그것이하고있는 펑키 한 일 때문에이 제한이 있다고 생각했습니다. elseifs로만 번역하는 경우 (한 포스터가 주석을 달았 듯이) 왜 우리는 case 문에서 변수를 허용하지 않습니까?

주의 사항 : 저는 사후 최적화 중입니다. 이 메서드는 앱의 느린 부분에서 여러호출 됩니다.


Greg의 프로필 결과는 그가 다룬 정확한 시나리오에 적합하지만 흥미롭게도 비교되는 유형의 수, 기본 데이터의 상대 빈도 및 패턴을 비롯한 여러 요인을 고려할 때 다양한 방법의 상대적 비용이 크게 변합니다. .

간단한 대답은 아무도 특정 시나리오에서 성능 차이가 무엇인지 말할 수 없다는 것입니다. 정확한 답을 얻으려면 자신의 시스템에서 다양한 방식으로 성능을 측정해야합니다.

If / Else 체인은 적은 수의 유형 비교에 효과적인 접근 방식이거나, 어떤 유형이 대부분의 유형을 구성할지 안정적으로 예측할 수있는 경우입니다. 접근 방식의 잠재적 인 문제는 유형의 수가 증가할수록 실행해야하는 비교의 수도 증가한다는 것입니다.

다음을 실행하면 :

int value = 25124;
if(value == 0) ...
else if (value == 1) ...
else if (value == 2) ...
...
else if (value == 25124) ... 

올바른 블록을 입력하기 전에 이전 if 조건을 각각 평가해야합니다. 반면에

switch(value) {
 case 0:...break;
 case 1:...break;
 case 2:...break;
 ...
 case 25124:...break;
}

올바른 코드 비트로의 간단한 점프를 수행합니다.

귀하의 예제에서 더 복잡 해지는 부분은 다른 방법이 조금 더 복잡 해지는 정수 대신 문자열에 스위치를 사용한다는 것입니다. 낮은 수준에서는 정수 값과 같은 방식으로 문자열을 켤 수 없으므로 C # 컴파일러가이 작업을 수행 할 수 있도록 몇 가지 마술을 수행합니다.

switch 문이 "충분히 작 으면"(컴파일러가 자동으로 최선이라고 생각하는 작업을 수행하는 경우) 문자열을 전환하면 if / else 체인과 동일한 코드가 생성됩니다.

switch(someString) {
    case "Foo": DoFoo(); break;
    case "Bar": DoBar(); break;
    default: DoOther; break;
}

와 같다:

if(someString == "Foo") {
    DoFoo();
} else if(someString == "Bar") {
    DoBar();
} else {
    DoOther();
}

사전에있는 항목 목록이 "충분히 커지면"컴파일러는 스위치의 문자열에서 정수 인덱스로 매핑 한 다음 해당 인덱스를 기반으로 스위치를 매핑하는 내부 사전을 자동으로 만듭니다.

다음과 같이 보입니다 (입력하는 것보다 더 많은 항목을 상상해보십시오).

정적 필드는 유형의 switch 문이 포함 된 클래스와 연관된 "숨겨진"위치에 정의되고 Dictionary<string, int>이름이 변경됩니다.

//Make sure the dictionary is loaded
if(theDictionary == null) { 
    //This is simplified for clarity, the actual implementation is more complex 
    // in order to ensure thread safety
    theDictionary = new Dictionary<string,int>();
    theDictionary["Foo"] = 0;
    theDictionary["Bar"] = 1;
}

int switchIndex;
if(theDictionary.TryGetValue(someString, out switchIndex)) {
    switch(switchIndex) {
    case 0: DoFoo(); break;
    case 1: DoBar(); break;
    }
} else {
    DoOther();
}

방금 실행 한 몇 가지 빠른 테스트에서 If / Else 방법은 3 가지 유형 (유형이 무작위로 배포되는 경우)에 대한 스위치보다 약 3 배 빠릅니다. 25 개 유형에서 스위치는 50 개 유형에서 작은 마진 (16 %)으로 더 빠릅니다. 스위치는 두 배 이상 빠릅니다.

많은 유형의 유형을 전환하려는 경우 세 번째 방법을 제안합니다.

private delegate void NodeHandler(ChildNode node);

static Dictionary<RuntimeTypeHandle, NodeHandler> TypeHandleSwitcher = CreateSwitcher();

private static Dictionary<RuntimeTypeHandle, NodeHandler> CreateSwitcher()
{
    var ret = new Dictionary<RuntimeTypeHandle, NodeHandler>();

    ret[typeof(Bob).TypeHandle] = HandleBob;
    ret[typeof(Jill).TypeHandle] = HandleJill;
    ret[typeof(Marko).TypeHandle] = HandleMarko;

    return ret;
}

void HandleChildNode(ChildNode node)
{
    NodeHandler handler;
    if (TaskHandleSwitcher.TryGetValue(Type.GetRuntimeType(node), out handler))
    {
        handler(node);
    }
    else
    {
        //Unexpected type...
    }
}

이것은 Ted Elliot이 제안한 것과 유사하지만 전체 유형 객체 대신 런타임 유형 핸들을 사용하면 리플렉션을 통해 유형 객체를로드하는 오버 헤드를 피할 수 있습니다.

내 컴퓨터의 몇 가지 빠른 타이밍은 다음과 같습니다.

5,000,000 개의 데이터 요소 (mode = Random) 및 5 가지 유형으로 3 회 반복 테스트
최적의 방법 시간 %
If / Else 179.67 100.00
TypeHandleDictionary 321.33 178.85
TypeDictionary 377.67 210.20
스위치 492.67 274.21

5,000,000 개의 데이터 요소 (mode = Random) 및 10 가지 유형으로 3 회 반복 테스트
최적의 방법 시간 %
If / Else 271.33 100.00
TypeHandleDictionary 312.00 114.99
TypeDictionary 374.33 137.96
스위치 490.33 180.71

5,000,000 개의 데이터 요소 (모드 = Random) 및 15 가지 유형으로 3 회 반복 테스트
최적의 방법 시간 %
TypeHandleDictionary 312.00 100.00
If / Else 369.00 118.27
TypeDictionary 371.67 119.12
스위치 491.67 157.59

5,000,000 개의 데이터 요소 (mode = Random) 및 20 가지 유형으로 3 회 반복 테스트
최적의 방법 시간 %
TypeHandleDictionary 335.33 100.00
TypeDictionary 373.00 111.23
If / Else 462.67 137.97
스위치 490.33 146.22

5,000,000 개의 데이터 요소 (mode = Random) 및 25 가지 유형으로 3 회 반복 테스트
최적의 방법 시간 %
TypeHandleDictionary 319.33 100.00
TypeDictionary 371.00 116.18
스위치 483.00 151.25
If / Else 562.00 175.99

5,000,000 개의 데이터 요소 (mode = Random) 및 50 가지 유형으로 3 회 반복 테스트
최적의 방법 시간 %
TypeHandleDictionary 319.67 100.00
TypeDictionary 376.67 117.83
스위치 453.33 141.81
If / Else 1,032.67 323.04

적어도 내 컴퓨터에서 유형 핸들 사전 접근 방식은 메서드에 대한 입력으로 사용되는 유형의 분포가 무작위 일 때 15 가지가 넘는 유형에 대해 다른 모든 유형을 능가합니다.

반면에 입력이 if / else 체인에서 가장 먼저 확인되는 유형으로 만 구성되어 있으면 해당 메서드가 훨씬 빠릅니다.

5,000,000 개의 데이터 요소 (mode = UniformFirst) 및 50 개 유형으로 3 회 반복 테스트
최적의 방법 시간 %
If / Else 39.00 100.00
TypeHandleDictionary 317.33 813.68
유형 사전 396.00 1,015.38
스위치 403.00 1,033.33

반대로, 입력이 항상 if / else 체인의 마지막 항목이면 반대 효과가 있습니다.

5,000,000 개의 데이터 요소 (mode = UniformLast) 및 50 개 유형으로 3 회 반복 테스트
최적의 방법 시간 %
TypeHandleDictionary 317.67 100.00
스위치 354.33 111.54
유형 사전 377.67 118.89
If / Else 1,907.67 600.52

입력에 대해 몇 가지 가정을 할 수 있다면 가장 일반적인 몇 가지 유형에 대해 if / else 검사를 수행 한 다음 실패 할 경우 사전 기반 접근 방식으로 대체하는 하이브리드 접근 방식에서 최상의 성능을 얻을 수 있습니다.


방금 빠른 테스트 응용 프로그램을 구현하고 ANTS 4로 프로파일 링했습니다.
사양 : 32 비트 Windows XP의 .Net 3.5 sp1, 코드는 릴리스 모드로 빌드되었습니다.

3 백만 테스트 :

  • 스위치 : 1.842 초
  • If : 0.344 초.

게다가, switch 문 결과는 (당연히) 긴 이름이 더 오래 걸린다는 것을 보여줍니다.

100 만 테스트

  • Bob : 0.612 초.
  • 질 : 0.835 초.
  • Marko : 1.093 초.

나는 "If Else"가 적어도 내가 만든 시나리오보다 더 빠른 것 같습니다.

class Program
{
    static void Main( string[] args )
    {
        Bob bob = new Bob();
        Jill jill = new Jill();
        Marko marko = new Marko();

        for( int i = 0; i < 1000000; i++ )
        {
            Test( bob );
            Test( jill );
            Test( marko );
        }
    }

    public static void Test( ChildNode childNode )
    {   
        TestSwitch( childNode );
        TestIfElse( childNode );
    }

    private static void TestIfElse( ChildNode childNode )
    {
        if( childNode is Bob ){}
        else if( childNode is Jill ){}
        else if( childNode is Marko ){}
    }

    private static void TestSwitch( ChildNode childNode )
    {
        switch( childNode.Name )
        {
            case "Bob":
                break;
            case "Jill":
                break;
            case "Marko":
                break;
        }
    }
}

class ChildNode { public string Name { get; set; } }

class Bob : ChildNode { public Bob(){ this.Name = "Bob"; }}

class Jill : ChildNode{public Jill(){this.Name = "Jill";}}

class Marko : ChildNode{public Marko(){this.Name = "Marko";}}

첫째, 당신은 사과와 오렌지를 비교하고 있습니다. 먼저 switch on type과 switch on string을 비교 한 다음 on type과 if on 문자열을 비교 한 다음 승자를 비교해야합니다.

둘째, 이것은 OO가 디자인 한 것입니다. OO를 지원하는 언어에서 유형 (모든 종류)을 켜는 것은 잘못된 디자인을 가리키는 코드 냄새입니다. 해결책은 추상 또는 가상 방법 (또는 언어에 따라 유사한 구조)을 사용하여 공통 기반에서 파생하는 것입니다.

예.

class Node
{
    public virtual void Action()
    {
        // Perform default action
    }
}

class Bob : Node
{
    public override void Action()
    {
        // Perform action for Bill
    }
}

class Jill : Node
{
    public override void Action()
    {
        // Perform action for Jill
    }
}

그런 다음 switch 문을 수행하는 대신 childNode.Action ()을 호출하면됩니다.


Switch 문은 if-else-if 래더보다 실행 속도가 빠릅니다. 이는 컴파일러가 switch 문을 최적화 할 수 있기 때문입니다. if-else-if 래더의 경우 코드는 프로그래머가 결정한 순서대로 각 if 문을 처리해야합니다. 그러나 switch 문 내의 각 사례는 이전 사례에 의존하지 않기 때문에 컴파일러는 가장 빠른 실행을 제공하는 방식으로 테스트 순서를 변경할 수 있습니다.


클래스를 만들었다면 switch 또는 elseif 대신 전략 디자인 패턴을 사용하는 것이 좋습니다.


각 개체에 대해 열거 형을 사용해보십시오. 열거 형을 빠르고 쉽게 켤 수 있습니다.


이미 이것을 작성하고 성능 문제가 있음을 발견하지 않았다면 어느 것이 더 빠른지 걱정하지 않을 것입니다. 더 읽기 쉬운 것을 사용하십시오. "조기 최적화는 모든 악의 근원입니다." -도널드 크 누스


SWITCH 구조는 원래 정수 데이터 용이었습니다. 의도는 포인터 테이블 인 "디스패치 테이블"에 대한 인덱스로 직접 인수를 사용하는 것이 었습니다. 따라서 단일 테스트가있는 다음 일련의 테스트가 아닌 관련 코드로 직접 시작됩니다.

여기서 어려운 점은 사용이 "문자열"유형으로 일반화되어 명백하게 인덱스로 사용할 수 없으며 SWITCH 구조의 모든 이점이 손실된다는 것입니다.

속도가 의도 한 목표라면 문제는 코드가 아니라 데이터 구조입니다. "이름"공간이 표시하는 것처럼 간단하다면 정수 값으로 코딩하고 (예 : 데이터가 생성 될 때) "앱의 느린 부분에서 여러 번"에서이 정수를 사용하는 것이 좋습니다.


전환하려는 유형이 기본 .NET 유형 인 경우 Type.GetTypeCode (Type)를 사용할 수 있지만 사용자 지정 유형 인 경우 모두 TypeCode.Object로 돌아옵니다.

델리게이트 또는 핸들러 클래스가있는 사전도 작동 할 수 있습니다.

Dictionary<Type, HandlerDelegate> handlers = new Dictionary<Type, HandlerDelegate>();
handlers[typeof(Bob)] = this.HandleBob;
handlers[typeof(Jill)] = this.HandleJill;
handlers[typeof(Marko)] = this.HandleMarko;

handlers[childNode.GetType()](childNode);
/// ...

private void HandleBob(Node childNode) {
    // code to handle Bob
}

switch ()는 else if 집합에 해당하는 코드로 컴파일됩니다. 문자열 비교는 유형 비교보다 훨씬 느립니다.


여러 참고서에서 if / else 분기가 switch 문보다 빠르다는 것을 읽은 기억이납니다. 그러나 Blackwasp에 대한 약간의 연구는 switch 문이 실제로 더 빠르다는 것을 보여줍니다. http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx

실제로 일반적인 3 ~ 10 개 (또는 그 정도의) 문을 비교하는 경우 둘 중 하나를 사용하여 실제 성능 향상이 있는지 심각하게 의심합니다.

Chris가 이미 말했듯이 가독성을 위해 이동하십시오. 더 빠른 것이 무엇입니까, 문자열을 켜거나 그렇지 않으면 문자를 입력합니까?


여기서 주된 성능 문제는 스위치 블록에서 문자열을 비교하고 if-else 블록에서 유형을 확인한다는 것입니다.이 두 가지는 동일하지 않기 때문에 "감자와 바나나를 비교하는 것"입니다.

나는 이것을 비교하여 시작합니다.

switch(childNode.Name)
{
    case "Bob":
        break;
    case "Jill":
        break;
    case "Marko":
      break;
}

if(childNode.Name == "Bob")
{}
else if(childNode.Name == "Jill")
{}
else if(childNode.Name == "Marko")
{}

다형성을 추구하는 것이 올바른 디자인이 얼마나 빨라질 수 있는지 잘 모르겠습니다.

interface INode
{
    void Action;
}

class Bob : INode
{
    public void Action
    {

    }
}

class Jill : INode
{
    public void Action
    {

    }
}

class Marko : INode
{
    public void Action
    {

    }
}

//Your function:
void Do(INode childNode)
{
    childNode.Action();
}

switch 문이 무엇을하는지 보는 것이 더 나은 도움이 될 것입니다. 함수가 실제로 유형에 대한 작업에 관한 것이 아니라면 각 유형에 대한 열거 형을 정의 할 수 있습니다.

enum NodeType { Bob, Jill, Marko, Default }

interface INode
{
    NodeType Node { get; };
}

class Bob : INode
{
    public NodeType Node { get { return NodeType.Bob; } }
}

class Jill : INode
{
    public NodeType Node { get { return NodeType.Jill; } }
}

class Marko : INode
{
    public NodeType Node { get { return NodeType.Marko; } }
}

//Your function:
void Do(INode childNode)
{
    switch(childNode.Node)
    {
        case Bob:
          break;
        case Jill:
          break;
        case Marko:
          break;
        Default:
          throw new ArgumentException();
    }
}

나는 이것이 문제의 두 접근 방식보다 빠르다고 가정합니다. 나노초가 중요하다면 추상 클래스 경로를 시도해 볼 수 있습니다 .


속도 차이를 강조하기 위해 솔루션을 보여주는 작은 콘솔을 만들었습니다. 다른 문자열 해시 알고리즘을 사용했습니다. 인증서 버전이 런타임에 저를 위해 느려지고 중복이 발생하지 않을 가능성이 높고 만약 그렇다면 내 switch 문이 실패 할 것입니다 (지금까지 발생하지 않음). 내 고유 한 해시 확장 방법은 아래 코드에 포함되어 있습니다.

출력이있는 Core 2 콘솔 앱

특히 중요한 코드를 사용할 때 695 틱 이상 29 틱을 취할 것입니다.

주어진 데이터베이스의 문자열 세트를 사용하여 코드에서 사용할 수 있도록 주어진 파일에 상수를 생성하는 작은 응용 프로그램을 만들 수 있습니다. 값이 추가되면 배치를 다시 실행하면 상수가 생성되고 해결책.

  public static class StringExtention
    {
        public static long ToUniqueHash(this string text)
        {
            long value = 0;
            var array = text.ToCharArray();
            unchecked
            {
                for (int i = 0; i < array.Length; i++)
                {
                    value = (value * 397) ^ array[i].GetHashCode();
                    value = (value * 397) ^ i;
                }
                return value;
            }
        }
    }

    public class AccountTypes
    {

        static void Main()
        {
            var sb = new StringBuilder();

            sb.AppendLine($"const long ACCOUNT_TYPE = {"AccountType".ToUniqueHash()};");
            sb.AppendLine($"const long NET_LIQUIDATION = {"NetLiquidation".ToUniqueHash()};");
            sb.AppendLine($"const long TOTAL_CASH_VALUE = {"TotalCashValue".ToUniqueHash()};");
            sb.AppendLine($"const long SETTLED_CASH = {"SettledCash".ToUniqueHash()};");
            sb.AppendLine($"const long ACCRUED_CASH = {"AccruedCash".ToUniqueHash()};");
            sb.AppendLine($"const long BUYING_POWER = {"BuyingPower".ToUniqueHash()};");
            sb.AppendLine($"const long EQUITY_WITH_LOAN_VALUE = {"EquityWithLoanValue".ToUniqueHash()};");
            sb.AppendLine($"const long PREVIOUS_EQUITY_WITH_LOAN_VALUE = {"PreviousEquityWithLoanValue".ToUniqueHash()};");
            sb.AppendLine($"const long GROSS_POSITION_VALUE ={ "GrossPositionValue".ToUniqueHash()};");
            sb.AppendLine($"const long REQT_EQUITY = {"ReqTEquity".ToUniqueHash()};");
            sb.AppendLine($"const long REQT_MARGIN = {"ReqTMargin".ToUniqueHash()};");
            sb.AppendLine($"const long SPECIAL_MEMORANDUM_ACCOUNT = {"SMA".ToUniqueHash()};");
            sb.AppendLine($"const long INIT_MARGIN_REQ = { "InitMarginReq".ToUniqueHash()};");
            sb.AppendLine($"const long MAINT_MARGIN_REQ = {"MaintMarginReq".ToUniqueHash()};");
            sb.AppendLine($"const long AVAILABLE_FUNDS = {"AvailableFunds".ToUniqueHash()};");
            sb.AppendLine($"const long EXCESS_LIQUIDITY = {"ExcessLiquidity".ToUniqueHash()};");
            sb.AppendLine($"const long CUSHION = {"Cushion".ToUniqueHash()};");
            sb.AppendLine($"const long FULL_INIT_MARGIN_REQ = {"FullInitMarginReq".ToUniqueHash()};");
            sb.AppendLine($"const long FULL_MAINTMARGIN_REQ ={ "FullMaintMarginReq".ToUniqueHash()};");
            sb.AppendLine($"const long FULL_AVAILABLE_FUNDS = {"FullAvailableFunds".ToUniqueHash()};");
            sb.AppendLine($"const long FULL_EXCESS_LIQUIDITY ={ "FullExcessLiquidity".ToUniqueHash()};");
            sb.AppendLine($"const long LOOK_AHEAD_INIT_MARGIN_REQ = {"LookAheadInitMarginReq".ToUniqueHash()};");
            sb.AppendLine($"const long LOOK_AHEAD_MAINT_MARGIN_REQ = {"LookAheadMaintMarginReq".ToUniqueHash()};");
            sb.AppendLine($"const long LOOK_AHEAD_AVAILABLE_FUNDS = {"LookAheadAvailableFunds".ToUniqueHash()};");
            sb.AppendLine($"const long LOOK_AHEAD_EXCESS_LIQUIDITY = {"LookAheadExcessLiquidity".ToUniqueHash()};");
            sb.AppendLine($"const long HIGHEST_SEVERITY = {"HighestSeverity".ToUniqueHash()};");
            sb.AppendLine($"const long DAY_TRADES_REMAINING = {"DayTradesRemaining".ToUniqueHash()};");
            sb.AppendLine($"const long LEVERAGE = {"Leverage".ToUniqueHash()};");
            Console.WriteLine(sb.ToString());

            Test();    
        }    

        public static void Test()
        {
            //generated constant values
            const long ACCOUNT_TYPE = -3012481629590703298;
            const long NET_LIQUIDATION = 5886477638280951639;
            const long TOTAL_CASH_VALUE = 2715174589598334721;
            const long SETTLED_CASH = 9013818865418133625;
            const long ACCRUED_CASH = -1095823472425902515;
            const long BUYING_POWER = -4447052054809609098;
            const long EQUITY_WITH_LOAN_VALUE = -4088154623329785565;
            const long PREVIOUS_EQUITY_WITH_LOAN_VALUE = 6224054330592996694;
            const long GROSS_POSITION_VALUE = -7316842993788269735;
            const long REQT_EQUITY = -7457439202928979430;
            const long REQT_MARGIN = -7525806483981945115;
            const long SPECIAL_MEMORANDUM_ACCOUNT = -1696406879233404584;
            const long INIT_MARGIN_REQ = 4495254338330797326;
            const long MAINT_MARGIN_REQ = 3923858659879350034;
            const long AVAILABLE_FUNDS = 2736927433442081110;
            const long EXCESS_LIQUIDITY = 5975045739561521360;
            const long CUSHION = 5079153439662500166;
            const long FULL_INIT_MARGIN_REQ = -6446443340724968443;
            const long FULL_MAINTMARGIN_REQ = -8084126626285123011;
            const long FULL_AVAILABLE_FUNDS = 1594040062751632873;
            const long FULL_EXCESS_LIQUIDITY = -2360941491690082189;
            const long LOOK_AHEAD_INIT_MARGIN_REQ = 5230305572167766821;
            const long LOOK_AHEAD_MAINT_MARGIN_REQ = 4895875570930256738;
            const long LOOK_AHEAD_AVAILABLE_FUNDS = -7687608210548571554;
            const long LOOK_AHEAD_EXCESS_LIQUIDITY = -4299898188451362207;
            const long HIGHEST_SEVERITY = 5831097798646393988;
            const long DAY_TRADES_REMAINING = 3899479916235857560;
            const long LEVERAGE = 1018053116254258495;

            bool found = false;
            var sValues = new string[] {
              "AccountType"
              ,"NetLiquidation"
              ,"TotalCashValue"
              ,"SettledCash"
              ,"AccruedCash"
              ,"BuyingPower"
              ,"EquityWithLoanValue"
              ,"PreviousEquityWithLoanValue"
              ,"GrossPositionValue"
              ,"ReqTEquity"
              ,"ReqTMargin"
              ,"SMA"
              ,"InitMarginReq"
              ,"MaintMarginReq"
              ,"AvailableFunds"
              ,"ExcessLiquidity"
              ,"Cushion"
              ,"FullInitMarginReq"
              ,"FullMaintMarginReq"
              ,"FullAvailableFunds"
              ,"FullExcessLiquidity"
              ,"LookAheadInitMarginReq"
              ,"LookAheadMaintMarginReq"
              ,"LookAheadAvailableFunds"
              ,"LookAheadExcessLiquidity"
              ,"HighestSeverity"
              ,"DayTradesRemaining"
              ,"Leverage"
            };

            long t1, t2;
            var sw = System.Diagnostics.Stopwatch.StartNew();
            foreach (var name in sValues)
            {
                switch (name)
                {
                    case "AccountType": found = true; break;
                    case "NetLiquidation": found = true; break;
                    case "TotalCashValue": found = true; break;
                    case "SettledCash": found = true; break;
                    case "AccruedCash": found = true; break;
                    case "BuyingPower": found = true; break;
                    case "EquityWithLoanValue": found = true; break;
                    case "PreviousEquityWithLoanValue": found = true; break;
                    case "GrossPositionValue": found = true; break;
                    case "ReqTEquity": found = true; break;
                    case "ReqTMargin": found = true; break;
                    case "SMA": found = true; break;
                    case "InitMarginReq": found = true; break;
                    case "MaintMarginReq": found = true; break;
                    case "AvailableFunds": found = true; break;
                    case "ExcessLiquidity": found = true; break;
                    case "Cushion": found = true; break;
                    case "FullInitMarginReq": found = true; break;
                    case "FullMaintMarginReq": found = true; break;
                    case "FullAvailableFunds": found = true; break;
                    case "FullExcessLiquidity": found = true; break;
                    case "LookAheadInitMarginReq": found = true; break;
                    case "LookAheadMaintMarginReq": found = true; break;
                    case "LookAheadAvailableFunds": found = true; break;
                    case "LookAheadExcessLiquidity": found = true; break;
                    case "HighestSeverity": found = true; break;
                    case "DayTradesRemaining": found = true; break;
                    case "Leverage": found = true; break;
                    default: found = false; break;
                }

                if (!found)
                    throw new NotImplementedException();
            }
            t1 = sw.ElapsedTicks;
            sw.Restart();
            foreach (var name in sValues)
            {
                switch (name.ToUniqueHash())
                {
                    case ACCOUNT_TYPE:
                        found = true;
                        break;
                    case NET_LIQUIDATION:
                        found = true;
                        break;
                    case TOTAL_CASH_VALUE:
                        found = true;
                        break;
                    case SETTLED_CASH:
                        found = true;
                        break;
                    case ACCRUED_CASH:
                        found = true;
                        break;
                    case BUYING_POWER:
                        found = true;
                        break;
                    case EQUITY_WITH_LOAN_VALUE:
                        found = true;
                        break;
                    case PREVIOUS_EQUITY_WITH_LOAN_VALUE:
                        found = true;
                        break;
                    case GROSS_POSITION_VALUE:
                        found = true;
                        break;
                    case REQT_EQUITY:
                        found = true;
                        break;
                    case REQT_MARGIN:
                        found = true;
                        break;
                    case SPECIAL_MEMORANDUM_ACCOUNT:
                        found = true;
                        break;
                    case INIT_MARGIN_REQ:
                        found = true;
                        break;
                    case MAINT_MARGIN_REQ:
                        found = true;
                        break;
                    case AVAILABLE_FUNDS:
                        found = true;
                        break;
                    case EXCESS_LIQUIDITY:
                        found = true;
                        break;
                    case CUSHION:
                        found = true;
                        break;
                    case FULL_INIT_MARGIN_REQ:
                        found = true;
                        break;
                    case FULL_MAINTMARGIN_REQ:
                        found = true;
                        break;
                    case FULL_AVAILABLE_FUNDS:
                        found = true;
                        break;
                    case FULL_EXCESS_LIQUIDITY:
                        found = true;
                        break;
                    case LOOK_AHEAD_INIT_MARGIN_REQ:
                        found = true;
                        break;
                    case LOOK_AHEAD_MAINT_MARGIN_REQ:
                        found = true;
                        break;
                    case LOOK_AHEAD_AVAILABLE_FUNDS:
                        found = true;
                        break;
                    case LOOK_AHEAD_EXCESS_LIQUIDITY:
                        found = true;
                        break;
                    case HIGHEST_SEVERITY:
                        found = true;
                        break;
                    case DAY_TRADES_REMAINING:
                        found = true;
                        break;
                    case LEVERAGE:
                        found = true;
                        break;
                    default:
                        found = false;
                        break;
                }

                if (!found)
                    throw new NotImplementedException();
            }
            t2 = sw.ElapsedTicks;
            sw.Stop();
            Console.WriteLine($"String switch:{t1:N0} long switch:{t2:N0}");
            var faster = (t1 > t2) ? "Slower" : "faster";
            Console.WriteLine($"String switch: is {faster} than long switch: by {Math.Abs(t1-t2)} Ticks");
            Console.ReadLine();

        }

문자열 비교는 항상 런타임 환경에 전적으로 의존합니다 (문자열이 정적으로 할당되지 않는 한, 서로 비교해야 할 필요성은 논란의 여지가 있습니다). 그러나 유형 비교는 동적 또는 정적 바인딩을 통해 수행 할 수 있으며 어느 쪽이든 문자열의 개별 문자를 비교하는 것보다 런타임 환경에서 더 효율적입니다.


확실히 String의 스위치는 유형 비교보다 느리고 스위치 / 케이스에 사용되는 일반적인 정수 비교보다 훨씬 느린 문자열 비교 (케이스 당 하나씩)로 컴파일됩니까?


세 가지 생각 :

1) 객체 유형에 따라 다른 작업을 수행하려는 경우 해당 동작을 해당 클래스로 이동하는 것이 합리적 일 수 있습니다. 그런 다음 switch 또는 if-else 대신 childNode.DoSomething ()을 호출합니다.

2) 유형 비교는 문자열 비교보다 훨씬 빠릅니다.

3) if-else 디자인에서 테스트 순서를 변경할 수 있습니다. "Jill"개체가 거기를 통과하는 개체의 90 %를 구성하는 경우 먼저 테스트합니다.


스위치와 관련된 문제 중 하나는 "Bob"과 같은 문자열을 사용하는 것입니다. 이로 인해 컴파일 된 코드에서 더 많은주기와 줄이 발생합니다. 생성 된 IL은 문자열을 선언하고 "Bob"으로 설정 한 다음 비교에 사용해야합니다. 이를 염두에두면 IF 문이 더 빨리 실행됩니다.

추신. Aeon의 예제는 유형을 켤 수 없기 때문에 작동하지 않습니다. (아니요 정확히 이유는 모르겠지만 시도해 보았지만 작동하지 않습니다. 유형이 변수 인 것과 관련이 있습니다.)

이것을 테스트하려면 별도의 응용 프로그램을 빌드하고 위에 작성된 작업을 수행하는 두 개의 간단한 메서드를 빌드하고 Ildasm.exe와 같은 것을 사용하여 IL을 확인하십시오. IF 문 Method의 IL에 훨씬 적은 줄이 있음을 알 수 있습니다.

Ildasm은 VisualStudio와 함께 제공됩니다 ...

ILDASM 페이지-http://msdn.microsoft.com/en-us/library/f7dy01k1(VS.80) .aspx

ILDASM 튜토리얼-http://msdn.microsoft.com/en-us/library/aa309387(VS.71) .aspx


프로파일 러는 당신의 친구라는 것을 기억하십시오. 추측은 대부분의 시간 낭비입니다. BTW, JetBrains의 dotTrace 프로파일 러에 대해 좋은 경험을했습니다 .


Switch on string은 기본적으로 if-else-if 래더로 컴파일됩니다. 간단한 것을 디 컴파일 해보십시오. 어쨌든 테스트 문자열 평등도는 인턴이되고 필요한 것은 참조 확인뿐이기 때문에 더 저렴해야합니다. 유지 관리 측면에서 타당한 일을하십시오. 문자열을 구성하는 경우 문자열 전환을 수행하십시오. 유형에 따라 선택하는 경우 유형 래더가 더 적합합니다.


저는 약간 다르게합니다. 여러분이 전환하는 문자열은 상수가 될 것이므로 컴파일 타임에 값을 예측할 수 있습니다.

귀하의 경우에는 해시 값을 사용하고 이것은 int 스위치이며 2 가지 옵션이 있으며 컴파일 시간 상수를 사용하거나 런타임에 계산합니다.

//somewhere in your code
static long _bob = "Bob".GetUniqueHashCode();
static long _jill = "Jill".GetUniqueHashCode();
static long _marko = "Marko".GeUniquetHashCode();

void MyMethod()
{
   ...
   if(childNode.Tag==0)
      childNode.Tag= childNode.Name.GetUniquetHashCode()

   switch(childNode.Tag)
   {
       case _bob :
        break;
       case _jill :
         break;
       case _marko :
        break;
   }
}

GetUniquetHashCode의 확장 메서드는 다음과 같을 수 있습니다.

public static class StringExtentions
    {
        /// <summary>
        /// Return unique Int64 value for input string
        /// </summary>
        /// <param name="strText"></param>
        /// <returns></returns>
        public static Int64 GetUniquetHashCode(this string strText)
        {
            Int64 hashCode = 0;
            if (!string.IsNullOrEmpty(strText))
            {
                //Unicode Encode Covering all character-set
                byte[] byteContents = Encoding.Unicode.GetBytes(strText);
                System.Security.Cryptography.SHA256 hash =  new System.Security.Cryptography.SHA256CryptoServiceProvider();
                byte[] hashText = hash.ComputeHash(byteContents);
                //32Byte hashText separate
                //hashCodeStart = 0~7  8Byte
                //hashCodeMedium = 8~23  8Byte
                //hashCodeEnd = 24~31  8Byte
                //and Fold
                Int64 hashCodeStart = BitConverter.ToInt64(hashText, 0);
                Int64 hashCodeMedium = BitConverter.ToInt64(hashText, 8);
                Int64 hashCodeEnd = BitConverter.ToInt64(hashText, 24);
                hashCode = hashCodeStart ^ hashCodeMedium ^ hashCodeEnd;
            }
            return (hashCode);
        }


    }

The source of this code was published here Please note that using Cryptography is slow, you would typically warm-up the supported string on application start, i do this my saving them at static fields as will not change and are not instance relevant. please note that I set the tag value of the node object, I could use any property or add one, just make sure that these are in sync with the actual text.

I work on low latency systems and all my codes come as a string of command:value,command:value....

now the command are all known as 64 bit integer values so switching like this saves some CPU time.


I was just reading through the list of answers here, and wanted to share this benchmark test which compares the switch construct with the if-else and ternary ? operators.

그 게시물 에 대해 내가 좋아하는 것은 단일 왼쪽 구성 (예 if-else:)뿐만 아니라 이중 및 삼중 수준 구성 (예 :)을 비교한다는 것 if-else-if-else입니다.

결과에 따르면이 if-else구성은 8/9 테스트 케이스에서 가장 빠릅니다. switch구조는 5/9 테스트 케이스에서 가장 빠른 공동.

따라서 속도를 찾고 있다면 if-else가장 빠른 방법으로 보입니다.


뭔가 빠졌을 수 있지만 문자열 대신 유형에 대한 switch 문을 수행 할 수 없습니까? 그건,

switch(childNode.Type)
{
case Bob:
  break;
case Jill:
  break;
case Marko:
  break;
}

참고 URL : https://stackoverflow.com/questions/94305/what-is-quicker-switch-on-string-or-elseif-on-type

반응형