generated from Learning-Is-Vital-In-Development/study-template
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Chapter 10 - 단형성 디스패치 & 온-스택 치환 (#24)
- Loading branch information
Showing
1 changed file
with
211 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
--- | ||
marp: true | ||
paginate: true | ||
--- | ||
|
||
# 단형성 디스패치와 온-스택 치환 알아보기 | ||
|
||
*Mono-Morphic Dispatch & On-Stack Replacement* | ||
|
||
⎯ 남궁민 | ||
|
||
--- | ||
|
||
## 헷갈리는 개념들을 이해해보자 | ||
|
||
챕터 10에서 다루는 핫스팟 JIT 컴파일러의 최신 최적화 기법들 | ||
|
||
- 인라이닝 | ||
- 루프 펼치기 | ||
- 탈출 분석 | ||
- 락 생략/확장 | ||
- 단일형 디스패치 | ||
- 인트린직 | ||
- 온-스택 치환 | ||
|
||
--- | ||
|
||
- ~~인라이닝~~ - 👌 | ||
- ~~루프 펼치기~~ - 👌 | ||
- ~~탈출 분석~~ - 👌 | ||
- ~~락 생략/확장~~ - 👌 | ||
- 단일형 디스패치 - *조금 헷갈리는데...* | ||
- ~~인트린직~~ - 👌 | ||
- 온-스택 치환 - *엥 뭐지?* | ||
|
||
--- | ||
|
||
## 단형성 디스패치 최적화 | ||
|
||
*'사람이 작성한 코드를 보면<br>십중팔구 각 호출부마다 딱 한 가지 런타임 타입이 수신자 객체 타입이 된다'* | ||
|
||
⇒ 메소드를 최초로 호출한 객체의 런타임 타입을 알아내면<br>그 이후 모든 호출도 동일한 타입일 가능성이 크다 | ||
|
||
--- | ||
|
||
### '다형성' 코드 예시 | ||
|
||
```java | ||
public int runMegamorphic() { | ||
Shape currentShape = null; | ||
|
||
switch (random.nextInt(3)) { | ||
case 0: | ||
currentShape = triangle; | ||
break; | ||
case 1: | ||
currentShape = square; | ||
break; | ||
case 2: | ||
currentShape = octagon; | ||
break; | ||
} | ||
} | ||
``` | ||
|
||
--- | ||
|
||
### 컴파일러가 단형성 디스패치 최적화를 하는 이유 | ||
|
||
- 다형성의 매우 약한 보장 | ||
- "object는 A, B, C 중에 하나이다." | ||
- 단형성 디스패치의 동작 방식은 하드웨어의 동작 방식과 가깝다 | ||
- 자바 같은 객체지향 언어는 **서브타입**과 **다형성**을 지원하는데, virtual invoke에 대한 하드웨어 지원은 없으므로 **메소드 디스패치**는 당연히 대가가 따른다 | ||
- 다형성 디스패치는 런타임 전에는 객체가 생성되지 않아서 어떤 메소드를 호출해야 할지 알 수가 없다 | ||
|
||
--- | ||
|
||
### 정적 메소드, 추상 클래스 구현체, 인터페이스 구현체 | ||
|
||
- 정적 메소드 > 동적 추상 클래스 구현체 > 동적 인터페이스 구현체 순으로 성능이 빠르다 | ||
- 정적 메소드는 **정적 바인딩**을 통해 메소드 디스패치를 생략하고, 어떤 메소드를 호출할지 바로 알 수 있기 때문 | ||
- 동적 추상 클래스 구현체의 경우, 가상 메소드를 호출할 때 null check 비용 발생 | ||
- 동적 인터페이스 구현체의 경우, 가상 메소드를 호출할 때 null check + 타입 체크 비용 발생 | ||
|
||
--- | ||
|
||
### 구현체별 벤치마크 결과 | ||
|
||
```sh | ||
Benchmark (count) (p1) (p2) (p3) Mode Cnt Score Error Units | ||
Three.dynamic_Abstract_Ref 10000 1 1 1 avgt 50 138.611 ± 2.524 us/op | ||
Three.dynamic_Interface_Ref 10000 1 1 1 avgt 50 155.505 ± 3.027 us/op | ||
Three.static_ID_ifElse 10000 1 1 1 avgt 50 99.868 ± 2.026 us/op | ||
Three.static_ID_switch 10000 1 1 1 avgt 50 98.905 ± 0.600 us/op | ||
|
||
Three.dynamic_Abstract_Ref 10000 18 1 1 avgt 50 79.674 ± 2.423 us/op | ||
Three.dynamic_Interface_Ref 10000 18 1 1 avgt 50 90.016 ± 0.940 us/op | ||
Three.static_ID_ifElse 10000 18 1 1 avgt 50 57.578 ± 1.079 us/op | ||
Three.static_ID_switch 10000 18 1 1 avgt 50 55.561 ± 0.169 us/op | ||
|
||
Three.dynamic_Abstract_Ref 10000 38 1 1 avgt 50 58.465 ± 0.160 us/op | ||
Three.dynamic_Interface_Ref 10000 38 1 1 avgt 50 58.335 ± 0.209 us/op | ||
Three.static_ID_ifElse 10000 38 1 1 avgt 50 51.692 ± 1.070 us/op | ||
Three.static_ID_switch 10000 38 1 1 avgt 50 51.860 ± 0.156 us/op | ||
``` | ||
|
||
--- | ||
|
||
### 단형성 디스패치 최적화 방식 | ||
|
||
- 정적 분석을 통해 다중 호출 타겟이 발생되었을 때 | ||
- 컴파일러는 동적 타입 프로필을 활용한 추측성 최적화를 통해 정확한 receiver 타겟을 찾아낼 수 있다 | ||
- C2 컴파일러는 단형성 및 이형성의 경우에 일반적으로 이 작업을 수행, 다형성의 경우에도 확실한 추정이 가능하면 수행 | ||
- C1 컴파일러는 단형성의 경우에만 이 작업을 수행 | ||
|
||
--- | ||
|
||
### `instanceof` 연산자 사용하기 | ||
|
||
```java | ||
if (currentShape instanceof Triangle) { | ||
return ((Triangle) currentShape).getSides(); | ||
} else { | ||
return currentShape.getSides(); | ||
} | ||
``` | ||
|
||
- 다형성 디스패치를 이형성 디스패치로 변환해서 최적화할 수 있다 | ||
|
||
--- | ||
|
||
### 코드를 작성할 때 다형성을 포기하라는 얘기는 아니다 | ||
|
||
- 대부분의 경우 메소드 디스패치에 대한 성능 오버헤드는 무시할 수 있을 정도로 작음 | ||
- 객체지향 언어에서 인터페이스를 작성하고 이에 대한 구현체를 작성하는 것은 매우 중요한 추상화 메커니즘 | ||
- 다만 메소드 디스패치 성능이 중요할 만한 소수의 예외 케이스들이 있을 수 있다 | ||
- 그래도 일반적인 상황에서 다형성에 대한 성능 걱정은 의미가 없다 | ||
|
||
--- | ||
|
||
## 온-스택 치환 | ||
|
||
### 온-스택 치환이 뭐야? | ||
|
||
- RET(Return Address): 함수 호출 이후 복귀 지점을 스택에 저장하는 명령어 | ||
- OSR(On-Stack Replacement): 함수 호출 이후 복귀 지점을 변경하는 명령어 | ||
- OSR은 길게 실행 중인 "핫" 메소드를 중간에 컴파일해서 최적화된 코드로 교체하는 기술 | ||
|
||
--- | ||
|
||
### OSR 단계 | ||
|
||
1. 런타임에서 "핫" 메소드를 '현재 변수값', '프로그램 카운터'를 통해 식별 | ||
2. 메소드를 recompile | ||
3. 컴파일러는 스택 활성화 프레임을 생성하고, 이전 버전에서 추출한 값들로 업데이트 | ||
4. 이전의 스택 활성화 프레임을 새로운 버전으로 교체하고 새 버전에서 실행 | ||
|
||
--- | ||
|
||
### OSR 최적화 확인 예제 | ||
|
||
```java | ||
public class OSRSample { | ||
public static void main(String[] args) { | ||
for (int i = 0; i < 100000; i++) { | ||
System.out.println("hello ->" + i); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
```sh | ||
java -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:LogFile=osrsample.log OSRSample.java | ||
``` | ||
|
||
--- | ||
|
||
```bash | ||
<nmethod compile_id='272' compile_kind='osr' compiler='c1' | ||
level='3' entry='0x000000010ce0a5c0' size='15200' | ||
address='0x000000010ce0a090' relocation_offset='336' insts_offset='1328' | ||
stub_offset='11312' scopes_data_offset='11864' scopes_pcs_offset='13552' | ||
dependencies_offset='14992' nul_chk_table_offset='15000' metadata_offset='11720' | ||
method='java.util.Properties$LineReader readLine ()I' bytes='584' count='49' | ||
backedge_count='62873' iicount='49' stamp='0.126'/> | ||
``` | ||
|
||
- `bytes` : 메소드 크기 | ||
- `compiled_kind='osr'` : OSR 기법을 사용했음을 나타냄 | ||
- `compiler='c1'` : C1 컴파일러를 사용했음을 나타냄 | ||
- `count` : 컴파일러에 의해 메소드가 호출된 횟수 | ||
- `backedge_count` : 루프가 포함된 메소드를 감지하는 용도, 인터프리터에 의해 숫자가 증가됨 | ||
- `iicount` : 인터프리터가 메소드를 호출한 횟수 | ||
|
||
--- | ||
|
||
### OSR의 또다른 작동 방식 | ||
|
||
- C2 컴파일러의 추측성 최적화가 실패할 수 있음 | ||
- 예시: 다형성 동적 메소드 호출을 정적 호출로 최적화했는데, 수신자 객체가 틀렸을 경우 | ||
- 이런 경우 최적화된 스택 프레임을 다시 최적화되지 않은 스택 프레임으로 교체한다 | ||
|
||
--- | ||
|
||
## References | ||
|
||
- [The Black Magic of (Java) Method Dispatch](https://shipilev.net/blog/2015/black-magic-method-dispatch/) | ||
- [What's up with monomorphism?](https://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html) | ||
- [최적화 기법 on-stack replacement](https://die4taoam.tistory.com/52) | ||
- [Java -On Stack Replacement (OSR)](https://thangavel-blog.medium.com/java-on-stack-replacement-osr-b527ab3fff8c) | ||
- [Differences between Just in Time compilation and On Stack Replacement](https://stackoverflow.com/questions/9105505/differences-between-just-in-time-compilation-and-on-stack-replacement) |