과연 inlining이 무엇이길래 다른 메소드들보다 final로 정의된 메소드가 빠르다는 것일까요? 먼저 final이 아닌 메소드들이 바이트코드 상에서 어떻게 저장이 되는지 먼저 설명을 하겠습니다. 자바는 알려진대로 객체지향 프로그래밍을 위해 특정 클래스를 확장하여 서브 클래스를 만들 수 있습니다. 그리고 해당 클래스들 내에서 메소드를 오버라이딩(overriding)이나 오버로딩(overloading)을 통해 여러가지 메소드들을 동일한 이름으로 사용하게 되지요.
가령 수퍼클래스에서는 foo(int x)라는 메소드를 선언한 다음에 서브클래스에서는 foo(int x)라는 동일한 메소드를 선언하여 해당 메소드를 재정의할 수 있습니다. 이것이 바로 오버라이딩이지요. 오버로딩은 foo(int x)에 foo(String y)와 같이 매개변수의 숫자나 데이터 형을 바꾸어서 사용하는 것을 의미합니다. 이렇게 선언된 메소드들은 메소드들의 시그니쳐(메소드이름+매개변수)에 따라 메소드 테이블이라는 곳에 해당 메소드들의 주소가 기억이 되게 됩니다. 그러므로 특정 오브젝트를 생성하고 메소드를 호출하면, 자바가상머신은 먼저 해당 오브젝트의 메소드 테이블을 검색하여 해당 메소드를 호출하게 됩니다. 만약 해당 오브젝트의 메소드 테이블에 메소드가 없으면 어떻게 될까요? 수퍼클래스의 메소드 테이블에서 메소드를 호출하게 됩니다. 만약 수퍼클래스에도 없으면, 당연히 예외(Exception)가 발생하겠지요.
final로 선언을 한 메소드는 위와 같이 메소드 테이블에 해당 메소드의 주소를 저장하지 않습니다. 대신에 메소드의 코드 자체를 메소드를 호출문 대신에 삽입하게 됩니다. 그래서 inlining이라고 부르는 것이지요. 어떤가요? 만약 상속을 여러번 거친 오브젝트의 경우 해당 메소드가 최상위 수퍼클래스에 존재한다면, 메소드 테이블을 여러번 검색해야하므로 당연히 퍼포먼스가 느려질 수 밖에 없습니다. 그러므로 final로 선언된 메소드는 메소드 호출에 따른 오버헤드와 동적 호출(dynamic dispatch)을 위한 메시지 테이블 검색에 따르는 오버헤드를 없애서 퍼포먼스를 증가시키게 됩니다.
한가지 더 재미있는 것은 가능하면, 메소드의 형태를 변수의 값으로 변화시켜서 메소드 내부의 오버헤드도 줄인다는 점입니다. 가령 다음과 같은 메소드가 있다고 가정해봅시다.
public final int returnThree(){
return 3;
}위와 같이 선언을 해주게 되면, 해당 메소드는 값 3으로 변환이 되게됩니다. 그러므로 메소드 내부의 코드들을 거쳐 값을 반환해주는 것보다 훨씬 빠른 퍼포먼스를 보여주게 됩니다.
하지만, Java SE 5.0 이상에서부터는 Just In Time 컴파일러를 통해 위와 같은 final 메소드에 대한 옵티마이징을 final이 아닌 메소드들에 대해서도 지원을 하게됩니다. 그러므로 final 메소드와 일반 메소드의 퍼포먼스 차이는 거의 없게 되지요. 하지만, 서브클래스의 오브젝트가 로딩이되어 해당 메소드를 오버로딩하게 되면, 옵티마이징 되었던 결과를 롤백하여 메시지 테이블로 변환하게 됩니다. 그러므로 퍼포먼스의 차이가 다시 생기게 되는 것이지요. 그 차이는 어떨까요? 예제를 하나 첨부하니 직접 테스트해보시지요.
결론적으로 설계를 할때, 빈번하게 사용되는 메소드는 가급적이면 서브클래스에서 오버라이딩을 시켜주지 않도록 하거나 아예 final로 지정하여 메소드 테이블에 등록이 되지 않도록 해주는 것이 좋습니다.
예제
public class FinalTest {
public static void main(String[] args) {
long beginTime;
long endTime;
Third third = new Third();
Fourth fourth = null;
beginTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
third.finalMethod();
if (i == 1000)
fourth = new Fourth();
}
endTime = System.currentTimeMillis();
long finalTime = endTime - beginTime;
System.out.printf("Final Method:" + finalTime);
System.out.println();
beginTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
third.nonFinalMethod();
if (i == 1000)
fourth = new Fourth();
}
endTime = System.currentTimeMillis();
long nonFinalTime = endTime - beginTime;
System.out.printf("Non Final Method:" + nonFinalTime);
}
}
class First {
public int nonFinalMethod() {
return 3;
}
public final int finalMethod() {
return 3;
}
}
class Second extends First {
public int test1() {
return 3;
}
}
class Third extends Second {
public int test1() {
return 3;
}
public int test2() {
return 3;
}
public int test3() {
return 3;
}
public int test4() {
return 3;
}
public int test5() {
return 3;
}
}
class Fourth extends Third {
public int test1() {
return 3;
}
public int test2() {
return 3;
}
public int test3() {
return 3;
}
public int test4() {
return 3;
}
public int test5() {
return 3;
}
public int nonFinalMethod() {
return 3;
}
}
public static void main(String[] args) {
long beginTime;
long endTime;
Third third = new Third();
Fourth fourth = null;
beginTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
third.finalMethod();
if (i == 1000)
fourth = new Fourth();
}
endTime = System.currentTimeMillis();
long finalTime = endTime - beginTime;
System.out.printf("Final Method:" + finalTime);
System.out.println();
beginTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
third.nonFinalMethod();
if (i == 1000)
fourth = new Fourth();
}
endTime = System.currentTimeMillis();
long nonFinalTime = endTime - beginTime;
System.out.printf("Non Final Method:" + nonFinalTime);
}
}
class First {
public int nonFinalMethod() {
return 3;
}
public final int finalMethod() {
return 3;
}
}
class Second extends First {
public int test1() {
return 3;
}
}
class Third extends Second {
public int test1() {
return 3;
}
public int test2() {
return 3;
}
public int test3() {
return 3;
}
public int test4() {
return 3;
}
public int test5() {
return 3;
}
}
class Fourth extends Third {
public int test1() {
return 3;
}
public int test2() {
return 3;
}
public int test3() {
return 3;
}
public int test4() {
return 3;
}
public int test5() {
return 3;
}
public int nonFinalMethod() {
return 3;
}
}

댓글 없음:
댓글 쓰기