자바 성능 최적화 3
- etc/리팩토링
- 2023. 6. 24.
3.2 메모리
- CPU의 클록 속도는 증가함.
- CPU 클록 속도만큼 데이터를 빠르게 가져오지 못함. 메모리 성능이 좋지 못함.
- 메모리 성능 문제를 극복하기 위해 CPU에 캐시 메모리가 추가됨.
- 멀티 코어에서는 캐시 메모리의 일관성을 잘 처리하기 위해 Cache Consistencty Protocol을 함.
- MESI 프로토콜
- 멀티 프로세서가 동시에 메모리를 Hit 하고 변경하는 경우라면, 다른 프로세서 관점에서 캐시는 invalid(무효)가 되어서 다시 처리됨.
- touchEveryItem() / touchEveryLine()은 i++ vs i += 16이기 때문에 16배의 일을 많이 할 것이라고 생각하지만, 실제 소요 시간은 거의 동일함.
- 캐싱 환경이기 때문에 이렇게 동작함. 이미 캐시에 모두 로딩되어 있기 때문임.
- 하지만 실제로 코드는 16번 실행되었을 듯?
3.3 최신 프로세서의 특성
- TLB : 가상 메모리 주소를 물리 메모리 주소로 매핑하는 페이지 테이블의 캐시 역할을 수행함. 이 녀석이 있어야 빈번한 작업 속도가 매우 빨라짐.
- 분기 예측과 추측 실행 : If 결과가 나올 때 까지 이전에는 CPU가 대기했었는데, 지금은 대략적으로 이렇게 동작할 것이다 추측하는 휴리스틱한 방법으로 처리함.
3.4 운영체제
- 스케쥴러
- 일반적으로 코어는 라운드 로빈 방식으로 점유함. 실행 큐 안에 들어가서 가장 앞에 있는 경우에 CPU를 점유할 수 있음.
- OS는 어플리케이션에게 CPU 사용 시간을 분배한다. 따라서 어플리케이션은 코드가 실행되는 시간보다 기다리는 시간이 더 많아질 수 있음.
- OS마다 이런 오버행 타임은 서로 다를 수 있음. 측정 방법은 다음과 같다.
- 1밀리초씩 총 1,000회 스레드를 재운다. → 이 때 추가된 시간을 보면 됨.
- 시간 문제
- 리눅스는 Unix 시간을 사용함.
- 윈도우는 1601년 이후 경과한 시간을 100나노초 단위로 기록함.
- 따라서 OS마다 사용되는 시간이 다를 수 있음.
- 컨텍스트 교환
- 유저 쓰레드가 커널 모드로 바꿔서 어떤 기능을 실행해야하는 경우, 큰 컨텍스트 스위칭이 발생함.
- 유저 공간에 있는 코드가 액세스 하는 메모리 영역 / 커널 영역의 메모리 영역은 공유할 부분이 없기 때문에 모드가 바뀌면 명령어와 다른 캐시를 어쩔 수 없이 강제로 비워야 함.
- 이런 부분을 최소화 하기 위해 리눅스는 VDOS라는 것을 추가 함.
- 리눅스 커널 특권이 필요 없는 시스템 콜의 경우, 굳이 커널 영역의 메모리를 사용할 필요가 없음.
- vDSO는 유저 공간의 메모리 영역임. 이 경우 시스템콜에서 커널로 컨텍스트 스위칭을 하지 않기 때문에 비용이 줄어듬.
3.6 기본 감지 전략
- 어플리케이션이 잘 돌아간다는 건 CPU 사용량 / 메모리 / 네트워크 / IO 대역폭 등 시스템 리소스를 효율적으로 잘 이용하고 있다는 뜻임.
- CPU 사용률
- vmstat, iostat을 이용해서 OS가 Raw하게 제공하는 정보를 살펴볼 필요가 있음.
- CPU 사용률이 100%에 근접하지 않았다면 왜 그럴까?를 따져야 함.
- 컨텍스트 교환 때문인가? I/O 경합이 일어나 블로킹이 발생했나?
- 유저 공간에서 CPU 사용률이 100% 사용하지 않은 채로, 컨텍스트 스위칭 비율이 높게 나타나면 I/O 블로킹 또는 쓰레드 락 경합 상황이 벌어졌을 가능성이 있음.
- 가비지 수집
- 핫스팟 JVM은 시작 시 메모리를 유저 공간에 할당 / 관리함. 따라서 메모리를 할당하는데 필요한 시스템 콜이 필요없음. 따라서 가비지 수집을 위해 커널 스위칭을 할 필요가 없음.
- 따라서 GC 자체는 유저 공간의 CPU 사이클을 소비함. (커널 공간의 CPU 사용률은 영향 없음)
- 만약 JVM 프로세스가 유저 공간에서 CPU를 100%에 가깝게 사용하고 있다면 GC를 의심해야 함.
- 성능 분석 시, 단순 툴에서 CPU 사용률이 100% 일정하지만 모든 사이클이 유저 공간에서 소비되고 있으면, CPU를 차지하는 것이 1. 유저 코드 ? 2. GC 인지 찾아봐야 함.
- 이 분석을 위해서 GC 로그를 넣어줘야 함.
3.8 JVM과 운영체제
- JVM은 자바 코드에 공용 인터페이스를 제공해서 OS에 독립적인 실행 환경을 제공한다. 하지만 이를 위해서 스레드 스케쥴링 같은 기본적인 서비스 조차도 하부 OS에 반드시 액세스 해야함.
- 이런 기능들은 native 키워드를 붙인 네이티브 메서드로 구현함.
- native 메서드는 C 언어로 작성하지만, 자바 메서드처럼 액세스 할 수 있음.
- 이 작업을 대행하는 공통 인터페이스를 JNI (Java Native Interface)라고 함.
- 예를 들어 System.currentTimeMillis()라는 메서드는 OS의 시간을 불러오는 작업이다. 이 작업은 다음과 같은 방식으로 실행됨
- Java : System.currntTimeMillis() (첫번째 호출)
- C : JVM_CurrentTimeMillis() (두번째로 호출됨)
- OS : OS::javaTimeMillis() (세번째로 호출됨)
- 플랫폼 : 플랫폼 특정 코드
'etc > 리팩토링' 카테고리의 다른 글
리팩토링 43. Assertions 추가하기 (1) | 2023.05.10 |
---|---|
냄새 24. 주석 (0) | 2023.05.10 |
냄새 23. 상속포기 (0) | 2023.05.10 |
리팩토링 42. 레코드 캡슐화 하기 (0) | 2023.05.10 |
냄새 22. 데이터 클래스 (0) | 2023.05.10 |