Java에서 if문과 switch문은 프로그래밍에서 자주 사용되는 조건 분기 구문이다. 이 둘 간에는 성능 차이가 있다. 3학년 때, 컴퓨터 구조 전공을 공부하면서 Switch문은 branch 명령이 없기 때문에 더 좋은 성능을 가진다라고 배웠던 기억이 있다. Java의 컴파일 코드를 보면서 위와 같은 관점으로 성능 차이가 작동하는 지 궁금해졌다.
따라서 이번 글에서는 JVM(+JIT)이 if문과 switch문을 어떻게 처리하는지 살펴보고, 그에 따른 성능 차이를 이해해보려고 한다.
"자바 컴파일러는 일반적으로 if-then-else문보다 switch 문에서 더 효율적인 바이트 코드를 생성한다."
원문: The Java compiler generates generally more efficient bytecode from switchstatements that use String objects than from chained if-then-else statements.
JAVA7 : Strings in switch Statements
Why is the switch statement faster than if else for String in Java 7?
Java의 JVM 컴파일 과정에서 switch문은 tableswitch 및 lookupswitch 명령을 사용하여 컴파일된다.
tableswitch 명령은 밀집된 케이스에서 효율적으로 작동한다.
lookupswitch 명령은 희소한 케이스에서 효율적으로 작동한다.
위 명령어들은 상수 시간 (O(1)
)으로 특정 케이스로 점프할 수 있게 해주므로, 케이스의 수와 무관하게 동일한 시간이 소요된다.
점프 테이블: 컴파일러는 값의 범위에 따라 점프 테이블을 생성하여, 실행 시에 해당 테이블을 참조해 바로 점프할 수 있는 최적화를 수행.
범위 검사: 점프 전에 주어진 값이 유효한 범위 내에 있는지 검사하고, 유효하지 않을 경우 default 구문으로 이동.
"스위치 문의 컴파일은 tableswitch 및 lookupswitch 명령을 사용합니다."
원문 : Compilation of switch statements uses the tableswitch and lookupswitch instructions.
JAVA17 3.10. Compiling Switches
int chooseNear(int i) {
switch (i) {
case 0: return 0;
case 1: return 1;
case 2: return 2;
default: return -1;
}
}
아래는 위의 java 코드를 컴파일한 코드이다. i 값에 대한 케이스가 맞다면 해당 줄로 jump
해버린다.
Method int chooseNear(int)
0 iload_1 // Push local variable 1 (argument i)
1 tableswitch 0 to 2: // Valid indices are 0 through 2
0: 28 // If i is 0, continue at 28
1: 30 // If i is 1, continue at 30
2: 32 // If i is 2, continue at 32
default:34 // Otherwise, continue at 34
28 iconst_0 // i was 0; push int constant 0...
29 ireturn // ...and return it
30 iconst_1 // i was 1; push int constant 1...
31 ireturn // ...and return it
32 iconst_2 // i was 2; push int constant 2...
33 ireturn // ...and return it
34 iconst_m1 // otherwise push int constant -1...
35 ireturn // ...and return it
O(log n)
시간 복잡도를 가짐.int chooseFar(int i) {
switch (i) {
case -100: return -1;
case 0: return 0;
case 100: return 1;
default: return -1;
}
}
아래는 위 java 코드를 컴파일한 코드이다.
Method int chooseFar(int)
0 iload_1
1 lookupswitch 3:
-100: 36
0: 38
100: 40
default: 42
36 iconst_m1
37 ireturn
38 iconst_0
39 ireturn
40 iconst_1
41 ireturn
42 iconst_m1
43 ireturn
O(n)
).if (x == 1) {
// do something
} else if (x == 2) {
// do something else
} else {
// default action
}
아래는 위의 코드 컴파일한 코드이다.
0: iload_1 // 로컬 변수 테이블에서 변수 x를 스택에 푸시
1: iconst_1 // 정수 1을 스택에 푸시
2: if_icmpeq 8 // 스택의 상위 두 개의 int 값을 비교하고, 두 값이 같으면(즉, x == 1이면) 프로그램의 흐름을 바이트코드의 8번째 줄로 이동시킴
5: iconst_2 // 정수 2를 스택 푸시합
6: if_icmpeq 14// 스택의 상위 두 개의 int 값을 비교하고, 두 값이 같으면(즉, x == 2이면) 프로그램의 흐름을 바이트코드의 14번째 줄로 이동시킴
9: // default action. 위의 모든 조건이 거짓이었을 경우 실행되는 기본 동작 코드
...
아래는 성능 측정을 위한 코드이다.
package Performance;
public class SwitchVSIf {
long startTime, endTime;
public void process() {
this.TestTableSwitch();
this.TestLookupSwitch();
this.TestIfElse();
}
private void TestTableSwitch() {
startTime = System.nanoTime();
for (int i = 0; i <= 100000; i++) {
switch (i % 10) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
break;
default:
break;
}
}
endTime = System.nanoTime();
System.out.println("Time for tableswitch: " + (endTime - startTime) + " ns");
}
private void TestLookupSwitch() {
startTime = System.nanoTime();
for (int i = 0; i <= 100000; i++) {
switch (i % 10) {
case 1:
case 3:
case 5:
case 7:
case 9:
break;
default:
break;
}
}
endTime = System.nanoTime();
System.out.println("Time for lookupswitch: " + (endTime - startTime) + " ns");
}
private void TestIfElse () {
startTime = System.nanoTime();
for (int i = 0; i <= 100000; i++) {
if (i % 10 == 1 || i % 10 == 2 || i % 10 == 3 || i % 10 == 4 || i % 10 == 5
|| i % 10 == 6 || i % 10 == 7 || i % 10 == 8 || i % 10 == 9 || i % 10 == 10) {
} else {
}
}
endTime = System.nanoTime();
System.out.println("Time for if-else: " + (endTime - startTime) + " ns");
}
}
위 코드 결과
tableswitch의 실행 시간: 2,862,400나노초
lookupswitch의 실행 시간: 2,255,000나노초
if-else의 실행 시간: 4,963,000나노초
**성능 : if-else < tableswitch < lookupswitch의**
컴퓨터 구조에서의 Control Flow
컴퓨터 구조로 보는 if문과 switch문
JVM로 보는 if문과 switch문
JIT 최적화와 'if' 문 제거에 대한 자세한 설명
'if' 문을 활용한 JVM 언어 생성 과정
JVM 해석과 컴파일에 대한 가이드