brunch

You can make anything
by writing

C.S.Lewis

by anonymDev Jan 26. 2022

로컬 변수 할당에 대한 단편적 생각

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를 발생한다고 말할 수 있을까? 현대 컴퓨팅 성능을 고려했을 때, 로컬 변수 한두 개 선언한다고 해서 메모리가 부족하다거나 성능이 안 좋아지지 않을 것이다. 로컬 변수 선언할까 말까를 고민할 시간에 비즈니스 로직에 관심을 두는 게 오히려 더 생산적일지도 모른다.


그냥 이렇게 접근해보면 어떨까? 로컬 변수를 선언하기 전에 정말 필요한 지 한 번쯤 고민해보면 좋지 않을까? 굳이 선언할 필요 없는 로컬 변수를 만들 필요도 없지 않은가.

이전 12화 동료 피드백에 대한 단상
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari