Chapter 05. 마이크로벤치마킹과 통계
Opened this issue · 3 comments
MinJunKweon commented
느낀점
- 통계 너무 어렵다
정리
- 벤치마킹은 최대한 공정하게 하는 것
- 최대한 시스템의 가변적인 부분은 테스트 간에 불변성을 유지해야함 (변인통제)
- 자바 런타임이 코드를 최적화하기 위해 많은 것을 함
- 그러므로 최적화가 미치는 영향을 구체적으로 이해하고 완전히 이해하고 설명하기란 불가능
벤치마킹 팁
- 실제 운영환경에서와 동일하게 JIT 컴파일러가 이미 최적화된 상태에서 테스트를 하기 위해 Warm-Up 후 테스트하는 것이 좋음
- GC는 사용자가 조절이 불가능하기 때문에 최대한 GC가 일어나지 않는 부분에서만 캡처를 진행하게 해야함
- 테스트하려는 코드를 자동 최적화해서 벤치마크 하려던 부분이 최적화될 위험이 있음
- 오차 범위를 구해서 수집한 값의 신뢰도를 파악하는 게 좋음
- 멀티스레드 코드 벤치마크는 매우 어려움. 하드웨어 경합이 발생할 수도 있음
- 시스템 전체를 벤치마크하는 방법도 있음. 작은 수준의 오차는 무시함
- 공통 프레임워크를 이용해 처리함하는 방법도 있음. JMH가 그런 툴
JVM 옵션 팁
-Xms2048m -Xmx2048m
: 최소/최대 힙 크기 조정하는 옵션 (메모리 옵션)-XX:+PrintComilation
: 메서드를 컴파일할 때마다(컴파일 이벤트가 발생할 때마다) 로깅하는 옵션-verbose:gc
: 가비지 컬렉션 로깅
마이크로벤치마킹
- 내가 짠 코드 이외의 원인으로 성능이 저하되었을 수 있다. (인프라 문제)
- 이 경우 마이크로벤치마킹은 알아차리기 어려운 문제가 있다.
- 마이크로 벤치마킹을 해야하는 경우
- 공통 범용 라이브러리를 개발할 때
- OpenJDK 또는 다른 자바 플랫폼 구현체를 개발할 때
- 지연에 극도로 민감한 코드를 개발할 때
자바 마이크로벤치마킹 툴 - JMH
- 위에서 언급한 마이크로 벤치마크
- 벤치마크 프레임워크가 반복을 여러번하면 루프 최적화를 수행할 수 있음
- JMH는 루프 최적화에 걸리지 않도록 반복횟수를 설정해서 루프 안에 감싸넣는 방식으로 동작함
JMH의 BlackHole
- 메소드 내에서 실행된 코드가 사이드 이펙트를 전혀 일으키지 않고 그 결과를 사용하지 않을 경우 해당 메서드를 삭제 대상으로 삼음
- 벤치마크 메소드가 반환한 단일 결과값을 암묵적으로 블랙홀에 할당함
- 블랙홀은 4가지 장치를 이용해 최적화를 못하게 보호함
- 런타임에 죽은 코드를 제거하는 최적화를 못하게함
- 반복되는 계산을 상수 폴딩하지 않게 만듦
- 값을 읽거나 쓰는 행위가 캐시 라인에 영향을 끼치는 잘못된 공유 현상 방지
- 쓰기 장벽으로부터 보호함
쓰기 장벽(Write Wall)
- 쓰기 리소스가 포화되어서 사실상 애플리케이션에 병목을 초래하는 지점
- 쓰기 장벽에 이르면 캐시에 영향을 미치고 쓰기 전용 버퍼가 오염될 수 있음
블랙홀 구현
public volatile int i1 = 1, i2 = 2;
public final void consume(int i) {
if (i == i1 & i == i2) {
// SHOULD NEVER HAPPEN
nullBait.i1 = i; // implicit null pointer exception
}
}
이 코드에 숨겨진 트릭은 다음과 같음:
- i1, i2가 volatile이기 때문에 반드시 런타임에 evaluation 되어야함
- if 문은 절대 true가 될 일은 없지만 컴파일러는 이 코드를 실행시켜야함
- if 문에 비트 연산자 AND가 있어서 추가 분기 로직이 문제될 일 없고 일정한 성능이 보장됨
public int tlr = (int) System.nanoTime();
public final void consume(Object obj) {
int tlr = (this.tlr = (this.tlr * 1664525 + 1013904223));
if (tlr & tlrMask) == 0) {
// SHOULD ALMOST NEVER HAPPEN IN MEASUREMENT
this.obj1 = obj;
this.tlrMask = (this.tlrMask << 1) + 1;
}
}
- 컴파일러가 탈출분석(Escape analysis)를 해보고 이 객체가 어느 객체와도 동등할 수 없다고 결론 내리면 비교문 자체가 retrun false로 최적화 될 수 있음
- 객체가 아주 드문 경우에만 실행된다는 조건하에 소비되도록 함
- 그래서 객체를 할당하지 않아도 소비될 수 있게 만드는 것
- 고도로 정확한 마이크로벤치마킹 툴을 개발하면서 클래스 문서화도 잘해놓았음
JMH의 강력한 기능
- 컴파일러를 제어함
- 벤치마크 도중 CPU 사용 수준을 시뮬레이션 함
- 실제 CPU 사이클을 소모해 다양한 CPU 부하 상황에서 벤치마크 시뮬레이션 가능
JVM 성능 통계
- 정확도(Accuracy) : 원인을 알 수 없는 요인이 상관관계 있는 형태로 측정되는 정도 (계통 오차)
- 정밀도(Precision) : 어떤 상관관계 없이 결과에 영향을 끼치는 오차가 얼마나 높은지 정도 (랜덤 오차)
계통 오차
- 백엔드 웹 서비스의 성능 테스트를 수행한다고 가정했을 때, 모든 요청이 전체적으로 느린 경우
- ex) 테스트 대상 서버는 영국이지만, 부하 테스트는 인도 뭄바이에서 한다면 이럴 수 있음
- 계통 효과가 커서 실제 응답 시간의 차이가 묻혀버림
- 테스트 설정이 잘못돼서 발생한 것이므로 환경을 다시 맞추고 수행하면 오차는 없어짐
랜덤 오차
- 원인을 알 수 없는 경우가 있음
- 대부분 정규 분포(가우시안 분포)를 따름
- 오차가 측정값에 미치는 긍정적/부정적 영향도가 얼추 비슷한 경우에는 적합하지만 JVM에는 이 모델이 잘 맞지 않음
허위 상관
- 상관관계가 있다고 인과관계를 나타내는 것은 아님
- 관련된 만화도 있어요ㅎㅎ https://yozm.wishket.com/magazine/detail/1913/
- 아무 관계도 없는 측정값을 보면 연관성이 전혀 없는 경우가 있지만 밀접한 상관관계가 있는 것처럼 보임
- ex) 아케이드 게임 총 매출액 대비 미국 컴퓨터 과학 박사 인원수의 상관관계
LOG-INFO commented
끄적끄적
- 마이크로 벤치마킹이란?
- 작은 코드 한 조각의 성능을 정확히 측정하는 것
- 고려해야할 것
- JVM 웜업 (JIT Compiler의 최적화)
- GC
- 해결 방법
- 시스템 전체를 벤치마킹 (저수준 수치는 무시)
- 연관된 저수준의 결과를 의미있게 비교하기 위해 공통 프레임워크를 이용해 처리
- ex. JMH
- 마이크로 벤치마킹을 하면 좋은 예 (사실상 대부분의 유즈케이스에 해당하지 않음)
- 총 코드 경로 실행 시간이 매우 적을 때(적어도 1ms, 실제론 100 microsec)
- 메모리 할당률 1MB/s 미만, 가급적 0에 가까운 값
- 100% 가까운 CPU 사용, sys는 10% 미만 (대부분 user)
- 실행 프로파일러로 CPU를 소비하는 메서드들의 분포를 확인했을 때, 가중치 높은 메서드는 많아야 2~3개
- JMH(Java Microbenchmark Harness): : JVM 언어로 작성된 nano/micro/milli/macro 벤치마크를 제작/실행/분석하는 자바 도구
- JVM을 개발한 사람들이 직접 만든 프레임워크
- 버전별 숨겨진 함정과 최적화 베어 트랩을 피하는 방법을 알고 있음
- JIT compiler는 죽은 코드를 삭제하여 최적화하는데, JMH에서는 이를 최적화하지 않도록 결괏값을 암묵적으로 '블랙홀'에 할당함
- 블랙홀: 무시해도 좋을 정더로 성능 오버헤드를 낮추려고 JMH 프레임워크 제작자가 개발
- 런타임에 죽은 코드를 제거하는 최적화를 못 하게 한다
- 반복되는 계산을 '상수 폴딩'하지 않게 한다 (컴파일 타임에 미리 계산 가능한 표현식을 상수로 바꾸어 최적화하는 과정)
- 값을 읽거나 쓰는 행위가 현재 캐시 라인에 영향을 끼치는 잘못된 공유 현상을 방지
- '쓰기 장벽'으로부터 보호
- 블랙홀: 무시해도 좋을 정더로 성능 오버헤드를 낮추려고 JMH 프레임워크 제작자가 개발
- 벤치마크 코드가 루프 최적화되지 않을 정도로 조심스레 반복 횟수를 설장한 루프 안에 감싸 넣음
- JIT compiler는 죽은 코드를 삭제하여 최적화하는데, JMH에서는 이를 최적화하지 않도록 결괏값을 암묵적으로 '블랙홀'에 할당함
- 제공하는 기능
- 컴파일러 제어
- ex)
@CompilerControl.Mode.DONT_INLINE
: 메서드를 인라이닝하지 않도록 설정@CompilerControl.Mode.INLINE
: 메서드를 인라이닝 하도록 설정@CompilerControl.Mode.EXCLUDE
: 메서드를 컴파일하지 않도록 설정
- ex)
- 벤치마크 도중 CPU 사용 수준을 시뮬레이션
- 컴파일러 제어
- 벤치마크가 성능을 정확히 반영하지 못한 부분은 없을지 체크하기 위해서는
이 테스트는 제대로 통제됐는가?
하는 질문을 던져보자- ex) GC 영향도
- 스스로 인지 편향에 흔들리지 앟ㄴ도록 관심을 기울이자
- 시스템 양상을 있는 그대로 잘 나타낸 측정값을 얻고 있는지 확인하자 (ex. GC 영향도)
- JVM 성능 통계
- 오차 유형
- Random error: 측정 오차 또는 무관계 요인이 어떤 상관관계 없이 결과에 영향을 미침
- Systematic error: 원인을 알 수 없는 요인이 상관관계 있는 형태로 측정에 영향을 미침
-
지금 내가 겪고 있는 이슈... 알 수 없이 GC가 많이 발생해서 API latency가 잠깐 오름
-
심증은 있으나 물증을 찾기 어려움 (중간중간 발생하는 Heavy bulk API or DB 문제)
-
-
- 허위 상관: 상관은 인과를 나타내지 않는다. 즉 두 변수가 비슷하다고 해서 연결고리가 있다고 볼 순 없다.
-
근데 사실 있는 경우가 99%이지 않나 싶음
-
허위 상관과 비슷한 예시로 이런 것도 있을 수 있으려나 (본문에서 말하고자 하는 것과 좀 다르긴 하지만)
-
배포 후 에러가 많이 나서 배포 문제인줄 알고 롤백했더니, 그 때 마침 이상한 데이터가 들어온게 문제였다거나 (실제로 몇 번 겪음)
-
-
- 오차 유형
만족스러워하는 대다수 고객의 경험보다 특이점을 유발하는 이벤트가 더 중요한 관심사
- 50th latency / 99th latency 그래프 두 개가 있는데, 주로 99th latency 그래프만 봄
새로 알게된 것
- JIT Compiler 최적화 종류
- 죽은 코드 삭제
- 상수 폴딩
- 등
- JMH(Java Microbenchmark Harness): : JVM 언어로 작성된 nano/micro/milli/macro 벤치마크를 제작/실행/분석하는 자바 도구
- JITWatch: JIT 컴파일러가 바이트코드를 갖고 무슨 일을 하는지 엿볼 수 있음
- 장벽 (wall; ex. 쓰기 장벽): 리소스가 포화돼서 사실상 애플리케이션에 병목을 초래하는 지점
minkukjo commented
새롭게 알게 된 것 위주로 정리
- 마이크로 벤치마킹 : 자바 코드 한 조각의 성능을 정확히 측정하는 것
- 벤치마킹 전 JVM 웜업과 GC를 항상 고려하자.
- 예전에 경력 이직할 때 라인의 광고 팀에 합격해서 갈까 말까 고민했었는데, 여기는 KPI가 요청 응답 속도인 아주 엔지니어링 스러운 팀이었다. 이 팀이라면 마이크로벤치마크를 사용해보기 적절할 것 같으나, 일반적으로 우리가 사용하는 서비스에선 이게 굳이 필요한가? 싶은 생각이었다.
- 위와 동일한 이유로 JMH 프레임워크를 읽으면서 음~ 그렇구나 하고 넘어갔음. ( 나중에 100% 까먹을 것 같았음 )
JVM 성능 통계
- 개발자가 성능 분석 시 흔히 맞닥뜨리는 두 주요 오차는 다음과 같음
- 랜덤 오차 : 측정 오차 또는 무관계 요인
- 계통 오차 : 원인을 알 수 없는 요인이 상관관계 있는 형태로 영향을 미침
느낀 점
- 개인적으로 마이크로 벤치마킹이 필요할 수 있지만, 아직까지 이게 필요했던 상황을 맞닥뜨려본적이 없어서 와닿지 않았다.. ㅜ
HaeUlNam commented
정리
- 과학적으로 순수하게 공정한 테스트는 현실적으로 어렵지만, 벤치마큰는 경험 결과의 근간을 형성하므로 최소한 반복은 할 수 있어야 함.
- JVM의 자동 최적화가 미치는 영향에 대해 구체적으로 이해하고 설명하기는 어렵다.
- Micro bench mark할 때 주의점
- JVM warm up time
- GC: GC가 동작하지 않을 때 benchmark하면 좋긴하지만, 가비지 수집은 원래 불확정적이어서 예측하기 어렵.
- 테스트하려는 코드에서 생성된 결과를 실제로 사용하지 않는 것. 이런 경우 죽은 코드로 간주해서 JIT 컴파일러가 최적화했을 가능성 있음
- 허용 오차를 구해 수집한 값의 신뢰도를 파악하는 게 좋다. 한번 측정한 결과로는 의미 없다
- 허용 오차: 오차 범위라고도 하며 설문 조사 등의 결과에서 랜덤 샘플링 오차의 양을 나타내는 통계학 용어.
- 해결방안
- 시스템 전체를 벤치 마크. 저수준 수치는 수집하지 않거나 무시
- 연관된 저수준의 결과를 의미있게 비교하기 위해 앞서 언급한 많은 문제를 공통 프레임워크를 이용해 처리
- 마이크로벤치마킹은 언제 하나?
- 사용 범위가 넓은 범용 라이브러리 코드를 개발한다
- OpenJDK 또는 다른 자바 플랫폼 구현체를 개발한다
- 지연에 극도로 민감한 코드를 개발한다(예: 저지연 거래)
→ Question: 두번째는 우리가 많이 접할 수 없는 Use case인 거 같고, 1번/3번은 마이크로벤치마킹보다 고수준의 metric 지표(Latency/CPU/Memory)등을 보는 게 효과적이지 않을까?
JVM 성능 통계
- 오차 유형
- 랜덤 오차(Random error): 측정 오차 또한 무관계 요인이 어떤 상관관계 없이 결과에 영향을 미침
- 랜덤 오차는 대부분 정규 분포 모델을 따르는데, JVM에는 이 모델이 잘 맞지 않음
- 정밀도: 랜덤 오차를 나타내는 말. 정밀도가 높으면 랜덤 오차가 낮은 것
- 계통 오차(Systematic error): 원인을 알 수 없는 요인이 상관관계 있는 형태로 측정에 영향을 미침
- 정확도: 계통 오차를 나타내는 말. 정확도가 높으면 계통 오차가 낮은 것
- 허위 상관: 상관은 인과를 나타내지 않는다
- 랜덤 오차(Random error): 측정 오차 또한 무관계 요인이 어떤 상관관계 없이 결과에 영향을 미침
- JVM이 생성한 Long-tail 비정규 분포를 다루는데, 유용한 기법은 Percentile(백분위) 개념을 조금 변용하는 것