if-else 문의 전환 장점
switch
문 을 사용하는 방법과 약 10 개의 작업이 예상되는 if
30 개의 unsigned
열거 형에 대한 문 을 사용하는 가장 좋은 방법은 무엇입니까 (현재는 같은 작업 임). 성능과 공간을 고려해야하지만 중요하지는 않습니다. 나는 발췌 문장을 추상화 했으므로 명명 규칙에 대해 나를 싫어하지 마십시오.
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 체인과 동일한 코드를 생성하므로 아무것도 잃지 않습니다. 의심스러운 경우 가장 일반적인 경우를 switch 문에 먼저 넣으십시오.
가장 좋은 경우 옵티마이 저는 코드를 생성하는 더 좋은 방법을 찾을 수 있습니다. 컴파일러가 수행하는 일반적인 작업은 이진 결정 트리를 작성하거나 (평균 경우 저장 및 점프) 점프 테이블을 작성하는 것입니다 (비교없이 작동).
예제에서 제공 한 특별한 경우 가장 명확한 코드는 다음과 같습니다.
if (RequiresSpecialEvent(numError))
fire_special_event();
분명히 이것은 문제를 코드의 다른 영역으로 옮기는 것이지만 이제는이 테스트를 재사용 할 기회가 있습니다. 또한 해결 방법에 대한 추가 옵션이 있습니다. std :: set을 사용할 수 있습니다. 예를 들면 다음과 같습니다.
bool RequiresSpecialEvent(int numError)
{
return specialSet.find(numError) != specialSet.end();
}
필자는 이것이 이것이 SpecialSpecialEvent의 최상의 구현이라고 제안하는 것이 아니라 옵션 일뿐입니다. 스위치 또는 if-else 체인, 조회 테이블 또는 값에 대한 비트 조작 등을 계속 사용할 수 있습니다. 의사 결정 프로세스가 불분명해질수록 격리 된 기능을 통해 더 많은 가치를 얻을 수 있습니다.
스위치 가 더 빠릅니다.
루프 내에서 30 개의 다른 값을 if / else-ing하고 스위치를 사용하여 동일한 코드와 비교하여 스위치가 얼마나 빠른지 확인하십시오.
이제 스위치에는 하나의 실제 문제가 있습니다 . 스위치는 컴파일 할 때 각 케이스 내부의 값을 알아야합니다. 이것은 다음 코드를 의미합니다.
// 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 ;
}
}
컴파일하지 않습니다.
대부분의 사람들은 정의 (Aargh!)를 사용하고 다른 사람들은 동일한 컴파일 단위로 상수 변수를 선언하고 정의합니다. 예를 들면 다음과 같습니다.
// 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 선언은 값이 변경되는 경우 다시 컴파일 할 필요가 없지만 스위치를 사용할 수 없도록합니다. 스위치를 사용 한다는 결론은 스위치 코드와 경우로 사용되는 변수 사이의 연결을 증가시킵니다 . 괜찮 으면 스위치를 사용하십시오. 그렇지 않을 때는 놀랄 일이 아닙니다.
.
2013-01-15 편집 :
Vlad Lazarenko 는 내 답변에 대해 언급하면서 스위치로 생성 된 어셈블리 코드에 대한 심층적 인 연구 링크를 제공했습니다. 매우 깨달음 : http://lazarenko.me/switch/
컴파일러는 어쨌든 그것을 최적화 할 것입니다-가장 읽기 쉬운 스위치로 이동하십시오.
가독성을위한 스위치입니다. 거대한 의견 진술은 유지하기가 어렵고 내 의견으로는 읽기가 어렵습니다.
ERROR_01 : // 의도적 인 넘어짐
또는
(ERROR_01 == numError) ||
후자는 오류가 발생하기 쉽고 첫 번째보다 더 많은 타이핑 및 서식이 필요합니다.
가독성을위한 코드. 성능이 더 좋은 것을 알고 싶다면 최적화와 컴파일러가 다양하고 성능 문제가 사람들이 생각하는 위치에 있기 때문에 프로파일 러를 사용하십시오.
스위치를 사용하십시오. 스위치는 프로그래머가 기대하는 것입니다.
나는 사람들에게 편안한 느낌을주기 위해 여분의 케이스 라벨을 넣을 것입니다.
다음 프로그래머가 언어 세부 사항에 대해 불필요한 생각을하지 않아도되기를 원하지 않습니다 (몇 달 안에있을 수도 있습니다).
컴파일러는 최적화에 정말 능숙 switch
합니다. 최근 gcc는 또한 여러 조건을 최적화하는 데 능숙합니다 if
.
나는 godbolt 에 대한 테스트 사례를 만들었습니다 .
때 case
값이 서로 가까이 그룹화, GCC, 그 소리, 그리고 ICC는 값이 특별한 사람 중 하나입니다 있는지 확인하는 비트 맵을 사용하는 모든 스마트 충분합니다.
예를 들어 gcc 5.2 -O3은 switch
to (그리고 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
mov / shift로 수행합니다. 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
해당 조작과 동작을 결합하기 위해). 따라서 비트 맵을 32 비트로 즉시 맞출 수 있으며, movabsq
명령 바이트를 더 많이 사용 하는 64 비트를 피할 수 있습니다.
(기계 코드에서) 더 짧은 순서는 다음과 같습니다.
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 (Bulldozer 이전)의 이점을 위해 분기 대상 및 조건부 분기에 사용됩니다. 'rep ret'은 무엇을 의미합니까? . 그렇지 않으면 오래된 CPU에서 분기 예측이 제대로 작동하지 않습니다.
bt
레지스터 인수가있는 (비트 테스트)가 빠릅니다. 1을 errNumber
비트 단위 로 왼쪽 이동하는 작업과을 수행하는 작업을 결합 test
하지만 여전히 1 사이클 대기 시간이며 단일 인텔 Uop입니다. 너무 많은 CISC 의미로 인해 메모리 인수가 느립니다. "비트 문자열"에 대한 메모리 피연산자를 사용하면 테스트 할 바이트의 주소가 다른 인수 (8로 나눔)를 기반으로 계산됩니다. 메모리 피연산자가 가리키는 1, 2, 4 또는 8 바이트 청크로 제한되지 않습니다.
에서 Agner 안개의 지시 테이블 , 가변 카운트 시프트 명령은보다 느린 bt
최근 인텔 (2 마이크로 연산 대신 1, 시프트가의 필요한 것을 다른 모든 일을하지 않음)에.
IMO 이것은 스위치 폴 스루가 이루어진 완벽한 예입니다.
미래에 사례가 그룹화되어있을 가능성이 높은 경우 (둘 이상의 사례가 하나의 결과에 해당하는 경우) 스위치를 읽고 유지 관리하기가 더 쉬울 수 있습니다.
그들은 똑같이 잘 작동합니다. 최신 컴파일러의 성능은 거의 같습니다.
더 읽기 쉽고 유연하기 때문에 case 문보다 if 문을 선호합니다. "|| max <min"와 같이 숫자 등식을 기반으로하지 않는 다른 조건을 추가 할 수 있습니다. 그러나 여기에 게시 한 간단한 사례는 실제로 중요하지 않으며 가장 읽기 쉬운 것을 수행하십시오.
스위치가 확실히 선호됩니다. 스위치의 케이스 목록을보고 long if 조건을 읽는 것보다 그것이 무엇을하고 있는지 아는 것이 더 쉽습니다.
상태의 복제 if
는 눈에 어렵습니다. 다음 중 하나 ==
가 작성되었다고 가정하십시오 !=
. 알아 차 릴까요? 또는 'numError'의 한 인스턴스가 'nmuError'로 작성된 경우 방금 컴파일 되었습니까?
나는 일반적으로 스위치 대신 다형성을 선호하지만 문맥에 대한 세부 사항이 없으면 말하기가 어렵습니다.
성능과 관련하여 가장 좋은 방법은 프로파일 러를 사용하여 예상했던 것과 유사한 조건에서 응용 프로그램의 성능을 측정하는 것입니다. 그렇지 않으면 잘못된 위치와 잘못된 방법으로 최적화하는 것입니다.
스위치 솔루션의 호환성에 동의하지만 IMO 는 스위치를 하이재킹하고 있습니다.
스위치의 목적은 값에 따라 다른 처리를하는 것입니다.
의사 코드로 알고리즘을 설명 해야하는 경우 의미 론적으로 if를 사용해야합니다 .if whatever_error 가이 작업을 수행합니다 ...
따라서 언젠가 각 오류에 대해 특정 코드를 갖도록 코드를 변경하지 않는 한 , if 사용할 것 입니다.
최선의 방법에 대해 잘 모르겠지만 스위치를 사용하고 'default'를 통해 의도적 인 폴 스루를 포착합니다.
미적으로 나는이 접근법을 선호하는 경향이있다.
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;
}
}
}
논리를 조금 어둡게 만들 수 있도록 데이터를 조금 더 똑똑하게 만드십시오.
I realize it looks weird. Here's the inspiration (from how I'd do it in 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)
Probably the first one is optimised by the compiler, that would explain why the second loop is slower when increasing loop count.
I would pick the if statement for the sake of clarity and convention, although I'm sure that some would disagree. After all, you are wanting to do something if
some condition is true! Having a switch with one action seems a little... unneccesary.
Im not the person to tell you about speed and memory usage, but looking at a switch statment is a hell of a lot easier to understand then a large if statement (especially 2-3 months down the line)
I would say use SWITCH. This way you only have to implement differing outcomes. Your ten identical cases can use the default. Should one change all you need to is explicitly implement the change, no need to edit the default. It's also far easier to add or remove cases from a SWITCH than to edit IF and ELSEIF.
switch(numerror){
ERROR_20 : { fire_special_event(); } break;
default : { null; } break;
}
Maybe even test your condition (in this case numerror) against a list of possibilities, an array perhaps so your SWITCH isn't even used unless there definately will be an outcome.
Seeing as you only have 30 error codes, code up your own jump table, then you make all optimisation choices yourself (jump will always be quickest), rather than hope the compiler will do the right thing. It also makes the code very small (apart from the static declaration of the jump table). It also has the side benefit that with a debugger you can modify the behaviour at runtime should you so need, just by poking the table data directly.
I know its old but
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);
}
}
Varying the loop count changes a lot:
While if/else: 5ms Switch: 1ms Max Loops: 100000
While if/else: 5ms Switch: 3ms Max Loops: 1000000
While if/else: 5ms Switch: 14ms Max Loops: 10000000
While if/else: 5ms Switch: 149ms Max Loops: 100000000
(add more statements if you want)
When it comes to compiling the program, I don't know if there is any difference. But as for the program itself and keeping the code as simple as possible, I personally think it depends on what you want to do. if else if else statements have their advantages, which I think are:
allow you to test a variable against specific ranges you can use functions (Standard Library or Personal) as conditionals.
(example:
`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";
However, If else if else statements can get complicated and messy (despite your best attempts) in a hurry. Switch statements tend to be clearer, cleaner, and easier to read; but can only be used to test against specific values (example:
`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;
I prefer if - else if - else statements, but it really is up to you. If you want to use functions as the conditions, or you want to test something against a range, array, or vector and/or you don't mind dealing with the complicated nesting, I would recommend using If else if else blocks. If you want to test against single values or you want a clean and easy to read block, I would recommend you use switch() case blocks.
참고URL : https://stackoverflow.com/questions/97987/advantage-of-switch-over-if-else-statement
'Programing' 카테고리의 다른 글
오류 항목-90167 패키지에 앱 번들이 없습니다. (0) | 2020.06.03 |
---|---|
pg_restore를 실행할 때“파일 헤더에서 [아카이버] 지원되지 않는 버전 (1.13)”얻기 (0) | 2020.06.03 |
지도와 사전의 차이점은 무엇입니까? (0) | 2020.06.03 |
YAML에서 빈 배열을 만들려면 어떻게합니까? (0) | 2020.06.03 |
요청 본문에 유효한 json을 게시하는 jQuery (0) | 2020.06.03 |