Java Bytecode로 살펴본 로컬 변수
최근에 코드 리뷰를 하며 아래와 같은 코드를 마주했다. 메서드의 반환 값을 로컬 변수로 할당해서 파라미터로 넘기는 코드이다.
public static void localVariableMethod() {
int number = NumberFunctionClass.incrementOne(1);
NumberFunctionClass.printNumber(number);
}
딱히 문제가 될 코드는 아니었지만 몇 가지 의문이 들었다.
하나, 메서드의 반환 값을 파라미터로 바로 넘기면 안 될까?
둘, 로컬 변수를 재사용하는 곳이 있을까?
셋, number라는 로컬 변수를 파라미터로 넘기는 게 가독성이 좋을까?
넷, 로컬 변수 선언은 오버헤드가 없을까?
아래와 같이 메서드의 반환 값을 바로 파라미터로 넘기면 어떨까?
public static void nonLocalVariableMethod() {
NumberFunctionClass.printNumber(NumberFunctionClass.incrementOne(1));
}
변수병 대신 메서드명을 쓰면 가독성을 해칠까?
변수를 꼭 재사용할 때만 선언해야 하는 걸까?
로컬 변수를 선언 안 하면 오버헤드를 줄일 수 있을까?
bytecode 수준에서 어떻게 다른지 확인해보았다.
javap -verbose NumberFunctionClass.class
우선 로컬 변수가 없는 코드를 먼저 보자
public static void nonLocalVariableMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_1
1: invokestatic #4 // Method incrementOne:(I)I
4: invokestatic #5 // Method printNumber:(I)V
7: return
LineNumberTable:
line 14: 0
line 15: 7
locals=0으로 보아 로컬 변수의 사이즈가 0으로 확인된다.
0:iconst_1은 상수 1을 stack에 올린다는 의미다(incrementOne의 파라미터로 상수 1을 넘겼다).
1: invokestatic #4 은 incrementOne를 실행한다는 의미다. 4: invokestatic #5는 printNumber 메서드를 실행하다는 의미다. 마지막으로 return 하면서 함수를 종료한다.
자! 이제 로컬 변수를 할당한 함수의 bytecode를 보자.
한눈에 보기로 코드의 길이가 길어졌다.
public static void localVariableMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=0
0: iconst_1
1: invokestatic #4 // Method incrementOne:(I)I
4: istore_0
5: iload_0
6: invokestatic #5 // Method printNumber:(I)V
9: return
LineNumberTable:
line 18: 0
line 19: 5
line 20: 9
LocalVariableTable:
Start Length Slot Name Signature
5 5 0 number I
locals=1으로 보아 로컬 변수의 사이즈가 1 늘어났다는 게 확인된다. 메모리 공간을 더 차지하는구나? 마지막 하단에 LocalVariableTable에 name이라는 로컬 변수가 위치한 것이 보인다. 또 어떤 게 달라졌을까?
incrementOne을 실행한 후에 아래 두줄의 코드가 추가된 점이 보인다.
4: istore_0 <----- 로컬 변수 테이블 0번 인덱스에 로컬 변수(int)를 저장한다는 의미다
5: iload_0 <----- 0번째 인덱스의 로컬 변수(int)를 스택에 로드한다는 의미다.
그리고 printNumber를 호출한다.
간단하게 말하면 (1) incrementOne의 반환 값을 iload를 통해서 LocalVariableTable에 저장했다. 그리고 (2) LocalVariableTable에 0번째에 저장됐던 변수(number)를 스택에 올린 후 printNumber를 호출했다. 로컬 변수를 할당했을 때 위 두 개의 operation이 추가적으로 발생한 것을 알 수 있다.
정리해보자.
로컬 변수 선언 안 하는 게 오버헤드가 작으니 성능 고려해서 로컬 변수 할당을 지양하자는 말이 하고 싶은 걸까? byte 단위의 operation이 method 실행에 overhead를 발생한다고 말할 수 있을까? 현대 컴퓨팅 성능을 고려했을 때, 로컬 변수 한두 개 선언한다고 해서 메모리가 부족하다거나 성능이 안 좋아지지 않을 것이다. 로컬 변수 선언할까 말까를 고민할 시간에 비즈니스 로직에 관심을 두는 게 오히려 더 생산적일지도 모른다.
그냥 이렇게 접근해보면 어떨까? 로컬 변수를 선언하기 전에 정말 필요한 지 한 번쯤 고민해보면 좋지 않을까? 굳이 선언할 필요 없는 로컬 변수를 만들 필요도 없지 않은가.