Android Froyo의 Dalvik VM JIT compile에 대해서
Android Froyo 버전의 Dalvik VM 부터는 JIT 컴파일러를 지원하여 VM의 성능이 약 5배 정도 향상되었다고 한다.
Google IO 2010에서 제공한 JIT 컴파일러 세션을 보고 완전하지는 않지만 부분적으로 이해한 내용을 정리해보고자 한다. 우선, 시간이 허락한다면 아래 세션 비디오를 먼저 살펴보시길.
Dalvik VM의 JIT 컴파일러는 Trace-granuality JIT 방식이다. 무슨 의미인고 하니 실행하는 바이트 코드 중에서 정말 반복이 심한 부분 영역만 골라서 컴파일한다는 것이다.
기존 Java가 제공하는 HotSpot 컴파일러의 경우 Profiling을 통해 빈번히 실행되는 Java Method를 컴파일해서 성능을 개선하지만 Dalvik VM은 Method 단위가 아닌 작은 코드 블럭(주로 반복문이 될 가능성이 높겠다)을 컴파일한다. 나름대로 장단점이 있겠지만 Google이 주장하기로는 모바일 장치에는 이 방식이 더 좋다고 한다. (뭐... 검증해보지 않아서 진실은 모르겠다.)
Google IO 세션에서 설명한 내용을 이용하여 좀 더 자세히 살펴보자.
위 그림에서 Start 지점에서는 컴파일할 대상 코드 블럭을 찾기위해 trace를 시작할 trace head 부분을 지정한다.
trace head로는 backward branch, method entry point, indirect branch와 같은 코드 부분을 이용한다. trace haed를 지정하였으면 이 위치에 대해 profile count를 관리하는데 코드 실행 path가 매번 이 지점을 다시 지나갈 때 profile count값을 증가시키게 된다.
profile count 값이 미리 정의된 threshold를 넘었는지 확인하고 그렇지 않다면 interpretation을 계속하고 만일, threshold를 넘었다면 현재 trace하고 있는 코드 블럭에 대한 translation이 존재하는지 확인한다. 지금은 처음 실행된 경우를 가정하니 당연히 translation이 존재하지 않는다.
UPDATE: profile count를 확인하는 과정은 Dalvik의 Mterp/out 폴더에 있는 InterpAsm ... 으로 시작하는 어셈블러 소스에 작성되어 있다. InterpAsm-armv5te.S 파일을 보면 9782 라인 쯤에 common_updateProfile 코드에서 profiling 과정을 처리한다.
이제 Dalvik VM은 trace building mode로 진입하고 interpretation을 진행한다. trace buidling 모드에서는 interpretation된 byte code들을 translation을 위해 계속 저장하다가 특정 사이즈만큼 모이면 compiler thread에서 컴파일하여 translation cache에 저장한다. 다음에 다시 동일 지점의 코드를 실행하면 translation cache에 컴파일되어 있는 코드를 이용하여 성능 향상이 이루어진다.
UPDATE: trace selection이라고 불리는 위 과정을 수행하는 코드는 Dalvik의 Jit.c 파일에 있는 dvmCheckJit 함수에서 이루어진다. interpreter mode가 kJitTSelect 상태이면 compile할 byte code를 선택하는 모드로 동작을 하고 특정 조건을 만족하면 (trace 크기가 어느 정도 이상이 되거나 unconditional한 branch가 발생하거나 ...) kJitTSelectEnd 상태로 interpreter mode를 변경하여 dvmCompilerWorkEnqueue 함수에 의해 compiler thread가 해당 trace를 compile할 수 있게끔 요청한다.
Google은 JIT가 사용하는 메모리의 크기를 약 100KB 정도로 아주 작게 사용한다고 했는데 좀 이상한 점은 translation cache 공간이 꽉차게되면 컴파일된 코드를 싹 지우고 다시 새로 시작한다는 점이다. translation cache 영역에 대해서는 아직 GC로 메모리 관리를 하지 못한다고 한다.
참고자료:
Google IO 2010에서 제공한 JIT 컴파일러 세션을 보고 완전하지는 않지만 부분적으로 이해한 내용을 정리해보고자 한다. 우선, 시간이 허락한다면 아래 세션 비디오를 먼저 살펴보시길.
Dalvik VM의 JIT 컴파일러는 Trace-granuality JIT 방식이다. 무슨 의미인고 하니 실행하는 바이트 코드 중에서 정말 반복이 심한 부분 영역만 골라서 컴파일한다는 것이다.
기존 Java가 제공하는 HotSpot 컴파일러의 경우 Profiling을 통해 빈번히 실행되는 Java Method를 컴파일해서 성능을 개선하지만 Dalvik VM은 Method 단위가 아닌 작은 코드 블럭(주로 반복문이 될 가능성이 높겠다)을 컴파일한다. 나름대로 장단점이 있겠지만 Google이 주장하기로는 모바일 장치에는 이 방식이 더 좋다고 한다. (뭐... 검증해보지 않아서 진실은 모르겠다.)
Google IO 세션에서 설명한 내용을 이용하여 좀 더 자세히 살펴보자.
위 그림에서 Start 지점에서는 컴파일할 대상 코드 블럭을 찾기위해 trace를 시작할 trace head 부분을 지정한다.
trace head로는 backward branch, method entry point, indirect branch와 같은 코드 부분을 이용한다. trace haed를 지정하였으면 이 위치에 대해 profile count를 관리하는데 코드 실행 path가 매번 이 지점을 다시 지나갈 때 profile count값을 증가시키게 된다.
profile count 값이 미리 정의된 threshold를 넘었는지 확인하고 그렇지 않다면 interpretation을 계속하고 만일, threshold를 넘었다면 현재 trace하고 있는 코드 블럭에 대한 translation이 존재하는지 확인한다. 지금은 처음 실행된 경우를 가정하니 당연히 translation이 존재하지 않는다.
UPDATE: profile count를 확인하는 과정은 Dalvik의 Mterp/out 폴더에 있는 InterpAsm ... 으로 시작하는 어셈블러 소스에 작성되어 있다. InterpAsm-armv5te.S 파일을 보면 9782 라인 쯤에 common_updateProfile 코드에서 profiling 과정을 처리한다.
9783 eor r3,rPC,rPC,lsr #12 @ cheap, but fast hash function 9784 lsl r3,r3,#(32 - JIT_PROF_SIZE_LOG_2) @ shift out excess bits 9785 ldrb r1,[r0,r3,lsr #(32 - JIT_PROF_SIZE_LOG_2)] @ get counter 9786 GET_INST_OPCODE(ip) 9787 subs r1,r1,#1 @ decrement counter 9788 strb r1,[r0,r3,lsr #(32 - JIT_PROF_SIZE_LOG_2)] @ and store it위와 같이 코드는 매우 간단하며 (당연히 성능을 고려하여 profiling 처리가 전체 성능에 최소한의 영향을 미치도록 작성하였다.) counter의 값을 감소하여 0이 되면 trace selection mode로 진입하게 된다. 참고로 counter의 값은 ARMv5의 경우는 200, ARMv7의 경우는 40으로 설정되어 있다.
이제 Dalvik VM은 trace building mode로 진입하고 interpretation을 진행한다. trace buidling 모드에서는 interpretation된 byte code들을 translation을 위해 계속 저장하다가 특정 사이즈만큼 모이면 compiler thread에서 컴파일하여 translation cache에 저장한다. 다음에 다시 동일 지점의 코드를 실행하면 translation cache에 컴파일되어 있는 코드를 이용하여 성능 향상이 이루어진다.
UPDATE: trace selection이라고 불리는 위 과정을 수행하는 코드는 Dalvik의 Jit.c 파일에 있는 dvmCheckJit 함수에서 이루어진다. interpreter mode가 kJitTSelect 상태이면 compile할 byte code를 선택하는 모드로 동작을 하고 특정 조건을 만족하면 (trace 크기가 어느 정도 이상이 되거나 unconditional한 branch가 발생하거나 ...) kJitTSelectEnd 상태로 interpreter mode를 변경하여 dvmCompilerWorkEnqueue 함수에 의해 compiler thread가 해당 trace를 compile할 수 있게끔 요청한다.
Google은 JIT가 사용하는 메모리의 크기를 약 100KB 정도로 아주 작게 사용한다고 했는데 좀 이상한 점은 translation cache 공간이 꽉차게되면 컴파일된 코드를 싹 지우고 다시 새로 시작한다는 점이다. translation cache 영역에 대해서는 아직 GC로 메모리 관리를 하지 못한다고 한다.
참고자료:
댓글
댓글 쓰기