If-else와 Switch는 조건문을 수행하기 위해 사용되는 구문이다.
이 둘의 차이는 뭘까? Baeldung의 "If-else and Switch"를 보고 번역+내 생각 을 정리해 봤다.
Switch block이 if-else 구문보다 더 읽기 쉬울 뿐만 아니라 유지관리하기도 더 쉽다. 조건문을 추가하려고 할 때, if-else에 추가하려면, 이전에 존재하던 조건문들을 모두 살펴봐야한다. 그러나, switch문은 case문 원하는 조건만 하나 추가해주면 된다. 때문에 코드를 변경하기가 쉽다.
-> 이해가 잘 안됐다. 왜 if-else는 이전에 존재하던 모든 조건문들을 살펴봐야 하고 switch는 그렇지 않지? switch도 이전에 존재하던 case들을 다 살펴봐야 하지 않나? 라고 생각했다. 이해해 보려 노력한 후 내린 결론은, 이런 말을 하고 싶었던 걸까? 생각해 본다.
// if문
String testValue = "test";
if(testValue == "live") {
...
} else if (testValue.subString(0,2) == "te") {
...
} else {
...
}
// switch문
switch(testValue){
case("live"): ...
case("test"): ...
}
switch문은 case문으로 == 밖에 검사하지 못하니 내가 원하는 분기가 있는지 없는지가 명확히 보여서 추가하기 쉽고, if-else는 온갖 연산을 써서 분기를 칠 수 있으니 하나의 조건 추가하려면 모든 조건을 하나하나 다 살펴봐야 한다. 뭐 이런 말을 하고 싶었던 것이려나..
대부분의 언어에서 쓰이는 switch문은, compile time에 case문의 value를 알고 있어야한다. 즉, case value로 변수를 쓰지 못한다는 말이다(단, Swift에서는 예외). 반면에, if-else는 run time에 if-else 분기를 해석하므로 변수를 써도 된다.
String test = "test";
String test2 = "test2";
switch(test){
case(test2): ...
}
이 코드를 실행시키면 error: constant string expression required 라는 에러가 뜬다.
Switch문에서 사람이 실수하면, falling case 문제를 겪을 수 있다. falling case 문제는, 각각의 case문에 실수로 break를 추가하지 않아 case문에 걸린 이후에 있는 모든 코드들이 수행되는 문제를 일컫는다(break문을 만날 때까지 모든 코드 수행함).
대부분의 compiler는 switch문을 "jump table"로 구현한다. Jump table은 요새 compiler들이 flow control을 이동시키기 위해 사용하는 abstract data structure이다. Jump table의 실제 구현은 case문의 개수에 따라 다른데, 소수의 case문만 있을 땐 dictionary나 map으로 구현되고, case문이 많아지면 hash table로 구현된다.
-> Jump table을 이용하게 되면, 적절한 case문을 jump 해가며 값을 match 시킬 수 있기 때문에 if-else문보다 효율적이다. 특히, case 조건이 [정수+연속]이면 index 그 자체로 table을 만들기도 하고, 그렇게 되면 binary search 같은 방법도 가능해지기 때문에 matching되는 값을 더 효율적으로 찾을 수 있다(그러나, trade-off로 if-else 보다 memory는 더 많이 쓰게 된다).
아래와 같은 simple한 switch문이 있다고 해보자.
switch (i)
{
case 1: printf("case 1"); break;
case 11: printf("case 11"); break;
case 111: printf("case 111"); break;
}
컴파일러는 각 case마다 다음과 같은 crud function을 만들 수 있다.
void case1() { printf("case 1"); }
void case11() { printf("case 11"); }
void case111() { printf("case 111"); }
그러면, runtime에 적절한 call을 하기 위해 function pointer를 사용한다.
typedef void (*pswitchfunccallback)(void);
pswitchfunccallback func_array[3] = {case1, case11, case111};
if ((unsigned)i<111)
func_array[i]();
이 때, Time complexity는 O(K)(단, K는 case block의 수)가 된다. 각 function의 address는 jump table에 저장되고, 최적화된 hash function에 의해 실행된다.
대부분의 complier는 if-else condition block을 위해 binary tree를 만든다. 전형적인 if-else문에 대해, 컴파일러는 이를 token list로 바꾸고, 그 토큰들로부터 추상 syntax tree를 생성한다. 이 트리는 나중에 비교를 통해 평가되어 특정 branch를 선택한다.
-> 흠.. 번역을 그대로 해보긴 했는데, 말이 어렵네. binary tree를 만들어서 비교하기 때문에 index jump를 하는 switch 보다 느리다 라는 말이 하고 싶은 것 같다.
일반적으로, 경우의 수가 많지 않고 각각의 경우가 동일한 빈도로 발생한다면 switch가 if-else보다 빠르다. 그러나, 경우의 수가 많고 각 값이 동일한 빈도로 들어오지 않는다면 if-else가 switch보다 빠를 수도 있다(대부분의 input value가 if-else 의 상단 case에 걸린다면..!). 그러나, 사실 둘의 execution time 차이는 매우 미미하다.
결론! switch를 사용하든 if-else를 사용하든 execution time은 거의 차이 없다...!
- if-else문은 Boolean data일 때 performance가 좋고, switch문은 fixed data에 대해 잘 동작한다.
- 스피드 측면에서는, 특정 몇몇 케이스에 input이 몰리면 if-else를, input이 모든 case에 균등하게 분포되면 switch를 선호한다.
-> 음.. fixed data가 어떤 걸 뜻하는 걸까. 정해진 데이터..? 고정된 데이터..? switch문이 왜 이런 데이터에 대해서 잘 동작하지? 나는 잘 이해 못하겠다. 그냥, case가 적을 땐 if-else가, case가 많을 땐 switch가 performance가 좋다 라고 이해하고 넘어가야겠다.
(fixed data values에 대한 의견이 있으시다면 댓글로 남겨주세요..! 큰 도움이 됩니당!)
이해를 위해 참고한 자료들
https://bigpel66.oopy.io/library/c/chewing-c/3
https://blog.naver.com/kki2406/80041410085
https://hee96-story.tistory.com/94