문제

사용하는 가장 좋은 방법은 무엇입니까? switch 진술과 사용 if 30에 대한 진술 unsigned 약 10개의 열거형에는 예상되는 작업(현재는 동일한 작업)이 있습니다.성능과 공간을 고려해야 하지만 중요한 것은 아닙니다.나는 그 조각을 추상화했으므로 명명 규칙 때문에 나를 미워하지 마십시오.

switch 성명:

// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing

switch (numError)
{  
  case ERROR_01 :  // intentional fall-through
  case ERROR_07 :  // intentional fall-through
  case ERROR_0A :  // intentional fall-through
  case ERROR_10 :  // intentional fall-through
  case ERROR_15 :  // intentional fall-through
  case ERROR_16 :  // intentional fall-through
  case ERROR_20 :
  {
     fire_special_event();
  }
  break;

  default:
  {
    // error codes that require no additional action
  }
  break;       
}

if 성명:

if ((ERROR_01 == numError)  ||
    (ERROR_07 == numError)  ||
    (ERROR_0A == numError)  || 
    (ERROR_10 == numError)  ||
    (ERROR_15 == numError)  ||
    (ERROR_16 == numError)  ||
    (ERROR_20 == numError))
{
  fire_special_event();
}
도움이 되었습니까?

해결책

스위치를 사용하세요.

최악의 경우 컴파일러는 if-else 체인과 동일한 코드를 생성하므로 아무것도 잃지 않습니다.의심스러우면 가장 일반적인 경우를 먼저 스위치 문에 넣으세요.

가장 좋은 경우에는 최적화 프로그램이 코드를 생성하는 더 나은 방법을 찾을 수 있습니다.컴파일러가 수행하는 일반적인 작업은 이진 결정 트리(평균적인 경우 비교 및 ​​점프 저장)를 구축하거나 단순히 점프 테이블(비교 없이 작동)을 구축하는 것입니다.

다른 팁

귀하의 예에서 제공한 특별한 경우의 경우 가장 명확한 코드는 아마도 다음과 같습니다.

if (RequiresSpecialEvent(numError))
    fire_special_event();

분명히 이것은 문제를 코드의 다른 영역으로 이동시키지만 이제 이 테스트를 재사용할 수 있는 기회가 있습니다.또한 문제를 해결하는 방법에 대한 더 많은 옵션이 있습니다.예를 들어 std::set을 사용할 수 있습니다.

bool RequiresSpecialEvent(int numError)
{
    return specialSet.find(numError) != specialSet.end();
}

나는 이것이 RequiresSpecialEvent의 최선의 구현이라고 제안하는 것이 아니라 단지 옵션일 뿐입니다.스위치나 if-else 체인, 조회 테이블 또는 값에 대한 일부 비트 조작 등을 계속 사용할 수 있습니다.의사결정 프로세스가 더 모호해질수록 이를 격리된 기능으로 활용함으로써 더 많은 가치를 얻을 수 있습니다.

스위치 ~이다 더 빠르게.

루프 내에서 30개의 서로 다른 값을 if/else로 시도하고 스위치를 사용하여 동일한 코드와 비교하여 스위치가 얼마나 빠른지 확인하십시오.

이제, 스위치에는 한 가지 진짜 문제가 있습니다 :스위치는 컴파일 타임에 각 케이스 내부의 값을 알아야 합니다.이는 다음 코드를 의미합니다.

// WON'T COMPILE
extern const int MY_VALUE ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

컴파일되지 않습니다.

그러면 대부분의 사람들은 정의(아아!)를 사용하고 다른 사람들은 동일한 컴파일 단위에서 상수 변수를 선언하고 정의할 것입니다.예를 들어:

// WILL COMPILE
const int MY_VALUE = 25 ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

따라서 결국 개발자는 "속도 + 명확성"과 "속도 + 명확성" 중에서 선택해야 합니다."코드 커플 링".

(스위치를 혼란스럽게 만들 수 없다는 말은 아닙니다...현재 내가 보는 대부분의 스위치는 "혼란스러운" 범주에 속합니다...하지만 이건 또 다른 이야기...)

2008-09-21 편집:

bk1e 다음 설명을 추가했습니다."헤더 파일에서 상수를 열거형으로 정의하는 것은 이를 처리하는 또 다른 방법입니다."

당연하지.

extern 유형의 요점은 소스에서 값을 분리하는 것입니다.이 값을 매크로, 간단한 const int 선언 또는 열거형으로 정의하면 값이 인라인되는 부작용이 있습니다.따라서 정의, 열거형 값 또는 const int 값이 변경되면 재컴파일이 필요합니다.extern 선언은 값이 변경될 경우 다시 컴파일할 필요가 없다는 의미이지만, 반면에 스위치를 사용할 수 없게 만듭니다.결론은 스위치를 사용하면 스위치 코드와 케이스로 사용되는 변수 간의 결합이 증가합니다..OK이면 스위치를 사용하십시오.그렇지 않은 경우에는 놀랄 일이 아닙니다.

.

2013-01-15 편집:

블라드 라자렌코 내 답변에 대해 스위치에 의해 생성된 어셈블리 코드에 대한 심층 연구에 대한 링크를 제공했습니다.매우 깨달음: http://741mhz.com/switch/

어쨌든 컴파일러는 그것을 최적화할 것입니다. 가장 읽기 쉬운 스위치로 이동하십시오.

가독성을 위해서만 스위치입니다.제 생각에는 거대한 if 문을 유지하기가 더 어렵고 읽기도 더 어렵습니다.

ERROR_01 :// 의도적인 폴스루

또는

(ERROR_01 == numError) ||

후자는 오류가 발생하기 쉽고 첫 번째보다 더 많은 입력과 형식 지정이 필요합니다.

가독성을 위한 코드입니다.무엇이 더 나은 성능을 발휘하는지 알고 싶다면 프로파일러를 사용하세요. 최적화와 컴파일러는 다양하고 사람들이 생각하는 성능 문제는 거의 발생하지 않습니다.

스위치를 사용하십시오. 이것이 바로 프로그래머가 기대하는 것입니다.

나는 여분의 케이스 라벨을 넣을 것입니다. 단지 사람들이 편안함을 느낄 수 있도록 하기 위해, 그것들을 제외하는 규칙이 언제/무엇인지 기억하려고 노력했습니다.
당신은 그것에 대해 작업하는 다음 프로그래머가 언어 세부 사항에 대해 불필요한 생각을 하는 것을 원하지 않습니다. (몇 달 안에 당신이 될 수도 있습니다!)

컴파일러는 최적화에 정말 능숙합니다. switch.최근 gcc는 또한 여러 조건을 최적화하는 데 능숙합니다. if.

나는 몇 가지 테스트 케이스를 만들었습니다. 신볼트.

case 값은 서로 가깝게 그룹화되어 있으며, gcc, clang 및 icc는 모두 비트맵을 사용하여 값이 특별한 값 중 하나인지 확인할 만큼 똑똑합니다.

예를 들어gcc 5.2 -O3은 다음을 컴파일합니다. switch (그리고 if 매우 유사한 것):

errhandler_switch(errtype):  # gcc 5.2 -O3
    cmpl    $32, %edi
    ja  .L5
    movabsq $4301325442, %rax   # highest set bit is bit 32 (the 33rd bit)
    btq %rdi, %rax
    jc  .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

비트맵은 즉각적인 데이터이므로 이에 대한 액세스 또는 점프 테이블에 대한 잠재적인 데이터 캐시 누락이 없습니다.

gcc 4.9.2 -O3은 다음을 컴파일합니다. switch 비트맵으로 변환하지만 1U<<errNumber 이동/교대.이는 다음을 컴파일합니다. if 버전을 일련의 분기로 만듭니다.

errhandler_switch(errtype):  # gcc 4.9.2 -O3
    leal    -1(%rdi), %ecx
    cmpl    $31, %ecx    # cmpl $32, %edi  wouldn't have to wait an extra cycle for lea's output.
              # However, register read ports are limited on pre-SnB Intel
    ja  .L5
    movl    $1, %eax
    salq    %cl, %rax   # with -march=haswell, it will use BMI's shlx to avoid moving the shift count into ecx
    testl   $2150662721, %eax
    jne .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

에서 1을 빼는 방법에 주목하세요. errNumber (와 함께 lea 해당 작업을 이동과 결합합니다).이를 통해 64비트 즉시를 피하고 비트맵을 32비트 즉시에 맞출 수 있습니다. movabsq 더 많은 명령어 바이트가 필요합니다.

더 짧은 (기계어 코드로) 순서는 다음과 같습니다.

    cmpl    $32, %edi
    ja  .L5
    mov     $2150662721, %eax
    dec     %edi   # movabsq and btq is fewer instructions / fewer Intel uops, but this saves several bytes
    bt     %edi, %eax
    jc  fire_special_event
.L5:
    ret

(사용 실패 jc fire_special_event 편재하며, 컴파일러 버그.)

rep ret 이전 AMD K8 및 K10(불도저 이전)의 이점을 위해 분기 대상 및 다음 조건부 분기에 사용됩니다. 'rep ret'은(는) 무슨 뜻인가요?.이것이 없으면 오래된 CPU에서는 분기 예측이 제대로 작동하지 않습니다.

bt (비트 테스트) 레지스터 arg를 사용하면 빠릅니다.이는 1을 왼쪽으로 이동하는 작업을 결합합니다. errNumber 비트와 일을 test, 그러나 여전히 1주기 대기 시간과 단일 Intel uop만 있습니다.너무 CISC 의미론 때문에 메모리 인수를 사용하면 속도가 느려집니다."비트 문자열"에 대한 메모리 피연산자를 사용하면 테스트할 바이트의 주소는 다른 인수(8로 나눔)를 기반으로 계산되며 가리키는 1, 2, 4 또는 8바이트 청크로 제한되지 않습니다. 메모리 피연산자에 의해.

에서 Agner Fog의 지침 테이블, 가변 카운트 시프트 명령어는 bt 최근 Intel에서는 1이 아닌 2개의 uop가 사용되며 Shift는 필요한 다른 모든 작업을 수행하지 않습니다.

IMO 이것은 스위치 폴스루가 만들어진 이유를 보여주는 완벽한 예입니다.

귀하의 사례가 나중에 그룹화되어 유지될 가능성이 있는 경우(둘 이상의 사례가 하나의 결과에 해당하는 경우) 스위치를 읽고 유지하는 것이 더 쉬울 수 있습니다.

그들은 똑같이 잘 작동합니다.최신 컴파일러를 고려하면 성능은 거의 동일합니다.

나는 if 문이 더 읽기 쉽고 유연하기 때문에 if 문을 선호합니다. " || max < min "과 같이 숫자 동등성을 기반으로 하지 않는 다른 조건을 추가할 수 있습니다.하지만 여기에 게시한 간단한 경우에는 별 문제가 되지 않습니다. 가장 읽기 쉬운 작업을 수행하면 됩니다.

스위치가 확실히 선호됩니다.긴 if 조건을 읽는 것보다 스위치의 사례 목록을 보고 그것이 무엇을 하는지 확실히 아는 것이 더 쉽습니다.

에서의 중복 if 상태가 눈에 힘듭니다.다음 중 하나를 가정해 보겠습니다. == 쓰여졌다 !=;눈치챌까?아니면 'numError'의 한 인스턴스가 방금 컴파일된 'nmuError'로 기록되었다면?

나는 일반적으로 스위치 대신 다형성을 사용하는 것을 선호하지만 컨텍스트에 대한 자세한 내용이 없으면 말하기 어렵습니다.

성능에 관해 가장 좋은 방법은 프로파일러를 사용하여 실제에서 기대하는 것과 유사한 조건에서 애플리케이션의 성능을 측정하는 것입니다.그렇지 않으면 아마도 잘못된 장소와 잘못된 방식으로 최적화하고 있을 것입니다.

나는 스위치 솔루션의 호환성에 동의하지만 IMO는 스위치를 탈취하다 여기.
스위치의 목적은 다음과 같습니다. 다른 값에 따라 처리합니다.
의사 코드로 알고를 설명해야 한다면 의미상 다음과 같기 때문에 if를 사용합니다. 뭐든지_오류가 있으면 이렇게 하세요...
따라서 언젠가 각 오류에 대한 특정 코드를 갖도록 코드를 변경하려는 경우가 아니면 다음을 사용합니다. 만약에.

최선의 방법은 잘 모르겠지만 스위치를 사용한 다음 '기본값'을 통해 의도적인 폴스루를 트랩합니다.

미학적으로 나는 이 접근 방식을 선호하는 경향이 있습니다.

unsigned int special_events[] = {
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20
 };
 int special_events_length = sizeof (special_events) / sizeof (unsigned int);

 void process_event(unsigned int numError) {
     for (int i = 0; i < special_events_length; i++) {
         if (numError == special_events[i]) {
             fire_special_event();
             break;
          }
     }
  }

데이터를 좀 더 똑똑하게 만들어서 논리를 좀 더 멍청하게 만들 수 있습니다.

나는 그것이 이상해 보인다는 것을 깨달았다.영감은 다음과 같습니다(Python에서 수행한 방법에서 영감을 얻음).

special_events = [
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20,
    ]
def process_event(numError):
    if numError in special_events:
         fire_special_event()
while (true) != while (loop)

아마도 첫 번째 루프는 컴파일러에 의해 최적화되었을 것입니다. 이는 루프 수를 늘릴 때 두 번째 루프가 더 느려지는 이유를 설명합니다.

명확성과 관례를 위해 if 문을 선택하겠습니다. 물론 일부는 동의하지 않을 수도 있습니다.결국, 당신은 뭔가를하고 싶어 if 어떤 조건이 참입니다!원액션으로 스위치를 사용하는 것은 약간...불필요하다.

나는 속도와 메모리 사용량에 대해 말할 사람은 아니지만 스위치 문을 보는 것이 큰 if 문보다 이해하기 훨씬 쉽습니다(특히 2-3개월 후에).

SWITCH를 사용한다고 말하고 싶습니다.이렇게 하면 서로 다른 결과만 구현하면 됩니다.10개의 동일한 케이스는 기본값을 사용할 수 있습니다.변경 사항을 명시적으로 구현하기만 하면 기본값을 편집할 필요가 없습니다.또한 IF 및 ELSEIF를 편집하는 것보다 SWITCH에서 사례를 추가하거나 제거하는 것이 훨씬 쉽습니다.

switch(numerror){
    ERROR_20 : { fire_special_event(); } break;
    default : { null; } break;
}

어쩌면 가능성 목록, 배열에 대해 조건(이 경우 numerror)을 테스트할 수도 있으므로 결과가 확실하게 나오지 않는 한 SWITCH가 사용되지 않을 수도 있습니다.

오류 코드가 30개만 있으므로 컴파일러가 올바른 작업을 수행하기를 바라기보다는 자신만의 점프 테이블을 코딩한 다음 모든 최적화 선택을 직접 수행합니다(점프가 항상 가장 빠름).또한 코드를 매우 작게 만듭니다(점프 테이블의 정적 선언은 제외).또한 디버거를 사용하면 필요한 경우 테이블 데이터를 직접 찌르는 것만으로 런타임 시 동작을 수정할 수 있다는 부수적인 이점도 있습니다.

나는 그것이 오래되었다는 것을 알고 있지만

public class SwitchTest {
static final int max = 100000;

public static void main(String[] args) {

int counter1 = 0;
long start1 = 0l;
long total1 = 0l;

int counter2 = 0;
long start2 = 0l;
long total2 = 0l;
boolean loop = true;

start1 = System.currentTimeMillis();
while (true) {
  if (counter1 == max) {
    break;
  } else {
    counter1++;
  }
}
total1 = System.currentTimeMillis() - start1;

start2 = System.currentTimeMillis();
while (loop) {
  switch (counter2) {
    case max:
      loop = false;
      break;
    default:
      counter2++;
  }
}
total2 = System.currentTimeMillis() - start2;

System.out.println("While if/else: " + total1 + "ms");
System.out.println("Switch: " + total2 + "ms");
System.out.println("Max Loops: " + max);

System.exit(0);
}
}

루프 횟수를 변경하면 많은 변화가 발생합니다.

if/else 동안:5ms 스위치 :1ms 최대 루프 :100000

if/else 동안:5ms 스위치 :3ms 최대 루프 :1000000

if/else 동안:5ms 스위치 :14ms 최대 루프 :10000000

if/else 동안:5ms 스위치 :149ms 최대 루프 :100000000

(원한다면 더 많은 설명을 추가하세요)

프로그램을 컴파일할 때 어떤 차이가 있는지는 모르겠습니다.하지만 프로그램 자체와 코드를 최대한 단순하게 유지하는 것에 관해서는 개인적으로 무엇을 하려는지에 달려 있다고 생각합니다.if else if else 문에는 다음과 같은 장점이 있습니다.

조건부로 함수 (표준 라이브러리 또는 개인)를 사용할 수있는 특정 범위에서 변수를 테스트 할 수 있습니다.

(예:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 if( a > 0 && a < 5)
   {
     cout<<"a is between 0, 5\n";

   }else if(a > 5 && a < 10)

     cout<<"a is between 5,10\n";

   }else{

       "a is not an integer, or is not in range 0,10\n";

그러나 If else if else 문은 (최선의 노력에도 불구하고) 서둘러 복잡하고 지저분해질 수 있습니다.Switch 문은 더 명확하고 깔끔하며 읽기 쉬운 경향이 있습니다.하지만 특정 값에 대해 테스트하는 데에만 사용할 수 있습니다(예:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 switch(a)
 {
    case 0:
    case 1:
    case 2: 
    case 3:
    case 4:
    case 5:
        cout<<"a is between 0,5 and equals: "<<a<<"\n";
        break;
    //other case statements
    default:
        cout<<"a is not between the range or is not a good value\n"
        break;

나는 if - else if - else 문을 선호하지만 실제로는 귀하에게 달려 있습니다.함수를 조건으로 사용하고 싶거나 범위, 배열 또는 벡터에 대해 무언가를 테스트하고 싶거나 복잡한 중첩을 처리하는 데 신경 쓰지 않는다면 If else if else 블록을 사용하는 것이 좋습니다.단일 값에 대해 테스트하고 싶거나 깔끔하고 읽기 쉬운 블록을 원한다면 switch() 케이스 블록을 사용하는 것이 좋습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top