class에서 final에 primitive 타입을 사용하면, 바이트 코드에서 그 값을 그대로 대입하여 성능 최적화를 받을 수 있다는 것을 저번 게시글에서 알아보았다.
이번에는 primitive 타입이 아니더라도 최적화를 받을 수 있는 부분을 알아보려 한다.
class Example {
static final Util util = new Util()
}
이전 포스트 내용에 따르면 해당 코드는 util이 가르켜야할 메모리 주소를 컴파일 단계에서 알 수 없기 때문에 최적화를 받을 수 없다.
하지만 Java Hotspot에서는 인터프리팅 뿐만 아니라 JIT 컴파일 방식도 사용한다.
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(3)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class JustInTimeConstants {
static final long x_static_final = Long.getLong("divisor", 1000);
static long x_static = Long.getLong("divisor", 1000);
final long x_inst_final = Long.getLong("divisor", 1000);
long x_inst = Long.getLong("divisor", 1000);
@Benchmark public long _static_final() { return 1000 / x_static_final; }
@Benchmark public long _static() { return 1000 / x_static; }
@Benchmark public long _inst_final() { return 1000 / x_inst_final; }
@Benchmark public long _inst() { return 1000 / x_inst; }
}
(출처: https://shipilev.net/jvm/anatomy-quarks/15-just-in-time-constants/)
위 코드를 통해 밴치마킹을 돌려보았다.

Score부분이 nano second 단위로 실행시간을 표시한것인데, static final이 압도적으로 좋은 성능을 보여준다.
JIT 컴파일러가 작동한 후의 바이트 코드를 살펴보면,
static final이 아닌 다른 경우:
mov 0x18(%r10),%r10 ; get field x_inst / x_inst_final
mov 0x70(%r10),%r10 ; get static x_static
static final을 사용했을때:
mov 0x8(%rsp),%rdx ; <--- slot holding the "long" constant "1"
static final의 경우 1000/1000을 수행하기 위해 static 변수를 참조하지 않고, 1000이라는 값 자체를 바이트 코드에 넣고, 여기서 1000/1000을 한번 더 최적화 시켜 1이라는 값을 사용하여 큰 최적화를 이룬다.
처음 java를 배울때 C언어보다 빠른 경우가 있다는게 상상이 안됐는데, 이번에 공부하면서 C언어에 비해 성능적인 이점을 얻는 부분이 분명히 있다는걸 알게 되었다.