JMH(Java 마이크로벤치마킹 하네스) 는 Couchbase Java SDK 및 관련 프로젝트 내에서 성능 최적화를 정량화(궁극적으로는 정당화)하는 데 매우 유용한 도구 중 하나가 되었습니다. 많은 사용자와 고객이 비슷한 질문으로 어려움을 겪고 있는 것을 보았기 때문에 다음과 같은 방법을 소개하는 것이 좋겠다고 생각했습니다. JMC(Java 미션 컨트롤) 를 JMH와 함께 사용하면 추측이 아닌 실제 데이터를 사용하여 프로파일링 및 JMC 성능 최적화 테스트 결정을 내릴 수 있습니다.
그냥 따라 읽으면서 소스 코드를 찔러볼 수도 있지만, 제가 보여드릴 내용을 재현하려면 최신 Oracle JDK(JMH의 경우 1.8.0_51을 사용하고 있습니다)가 필요합니다. 또한 프로젝트를 IDE로 가져오는 방법과 같은 사소한 세부 사항은 모두 보여드리지 않겠습니다. Java 성능 프로파일링의 기본에 어려움을 겪고 계신다면 이 포스팅이 적합하지 않을 수도 있습니다.
대부분의 실제 애플리케이션과 라이브러리의 문제점은 뚜렷한 해결 방법이 없다는 것입니다. 무언가를 발견하면 스스로를 토닥이며 고칠 수 있는 기회는 드뭅니다. 더 나은 결과를 얻기 위해 많은 작은 개선이 필요한 경우가 많습니다. Java SDK에서는 지연 시간을 최대한 최소화하면서 처리량을 늘리는 데 중점을 둡니다. 두 속성은 서로를 특별히 좋아하지 않기 때문에 두 가지를 동시에 만족시킬 수 있는 방법은 몇 가지 밖에 없습니다:
- 애플리케이션의 계산에 필요한 CPU 사용량 줄이기
- 경합 및 동기화 지점 최소화
- GC의 작업을 줄이기 위해 오브젝트 할당을 피하세요.
이 예제에서는 3번(그리고 1번)이 작동하는 것을 볼 수 있습니다. core-io 프로젝트에서 다음 코드를 실행해 보았습니다:
제 강연을 보신 적이 있으시다면, 저는 항상 이런 것은 적절한 벤치마크가 아니라고 말씀드리는데, 왜 여기서 벤치마킹을 하는 걸까요? 가장 큰 차이점은 사용자로서 Couchbase 전체를 벤치마킹할 때는 프로덕션 시스템을 모방하는 현실적인 워크로드가 아니라는 점입니다. 라이브러리 개발자로서 저는 핫 코드 경로를 찾아서 더 빠르게 만들고 싶기 때문에 동일한 코드를 반복해서 실행하면 JVM(Java) 프로파일러에서 해당 경로가 더 잘 노출됩니다.
로컬 Couchbase Server 노드에서 이 코드를 실행하면 약 9천 작업/초가 나옵니다. 이 시점에서 처리량을 늘리는 데는 관심이 없고, 애초에 핫 코드 경로에서 객체 할당을 줄이려고 합니다. JMC를 시작하고 JFR(Java Flight Recorder)을 통해 JVM을 가리킨 다음 프로파일링하는 동안 머신을 5분 동안 그대로 두면 다음과 같은 결과가 나왔습니다:
키값 상태[] 객체에는 5분 동안 400MB가 할당되어 있습니다. 이제 이것이 분명하지 않을 수도 있지만 최근에 해당 코드 경로에서 무언가를 변경했다는 것을 알고 있습니다( 여기) 그리고 이런 유형의 배분은 전에는 본 적이 없습니다. 빠른 조사 끝에 용의자가 다음과 같은 코드 경로에 있다는 것이 분명해졌습니다(JFR이 우리를 올바른 장소로 안내하는 방법에 주목하세요):
응답이 도착할 때마다 KeyValueStatus 열거형과 일치시켜 적절한 응답 상태를 설정합니다. 제 동료 중 한 명이 리팩토링하는 동안 코드를 보기 좋게 만들었지만, GC 오버헤드가 매우 미묘하게 발생했습니다. 프로파일링 정보에서 볼 수 있듯이 values()는 항상 배열을 생성하므로 이를 반복하여 일치하는 코드를 찾을 수 있습니다. 많지는 않지만 시간이 지남에 따라 합산됩니다. 하지만 가장 중요한 것은 불필요한 쓰레기가 발생한다는 점입니다.
결과적으로 일부 응답은 다른 응답보다 발생할 가능성이 (훨씬) 높으므로 일반적인 경우에 최적화할 수 있습니다(항상 좋은 생각입니다). 모든 낮은 수준의 내용을 지루하게 설명하지는 않겠지만, 일반적으로 GET의 경우 문서가 존재하면 SUCCESS를 반환하고 문서가 존재하지 않으면 NOT_FOUND를 반환합니다. 따라서 우리가 할 수 있는 한 가지는 다음과 같습니다:
코드를 변경하고 JFR을 다시 실행하면 배열 할당이 사라집니다:
대부분의 경우 개발자는 이 지점에서 멈추는 경향이 있지만, 계속 읽어보시기를 권합니다. 배열 할당을 피했다고 해서(이는 좋은 일입니다) 속도가 빨라지는 것은 아니죠? 일반적으로는 그렇게 믿겠지만, 저는 과거에 그렇게 믿기에는 너무 많이 놀랐던 적이 있습니다. 설령 우리가 옳다고 하더라도, 빠른 JMH 마이크로 벤치마크를 통해 알아보는 것은 좋은 일입니다. 또한 이미 확인한 N°3뿐만 아니라 위에서 언급한 N°1에 대한 개선 사항을 정량화할 수 있습니다.
JMH는 OpenJDK 프로젝트의 일부이며 사용하기 어렵지 않습니다. 홈페이지에서 찾을 수 있는 mvn archetype:generate 명령을 사용하여 프로젝트를 설정할 수 있습니다. 스켈레톤 프로젝트가 생성되면 Java SDK를 종속성으로 추가하거나(자동으로 섀도 처리됨) 열거형만 복사하는 두 가지 선택지가 있습니다. 테스트의 규모와 테스트하려는 항목에 따라 둘 중 하나를 선택하면 됩니다. 현재 테스트는 독립형이므로 열거형을 프로젝트에 복사할 수 있습니다. 이렇게 하면 진행하면서 수정하고 새로운 메서드를 추가하여 여러 가지를 시도해 볼 수 있다는 이점이 있습니다.
2.2.0 버전에서 KeyValueStatus 클래스를 복사하여 두 가지 변형을 동시에 호출할 수 있도록 코드를 변경했습니다:
이제 (간단한) JMH 벤치마크를 작성할 수 있습니다:
다음으로 코드를 컴파일하고 명령줄에서 실행합니다. 먼저 "mvn clean install"을 실행한 다음 "java -jar target/benchmarks.jar -wi 10 -f1 -i 10 -bm avgt -tu ns"를 통해 시작하세요.
너무 난해하게 느껴진다면 평균 지연 시간을 나노초 단위로 측정하고 하나의 포크에서 10회의 워밍업 반복과 10회의 측정 반복을 수행한다는 의미입니다. 궁금하신가요? 결과는 다음과 같습니다:
모든 연산은 나노초 미만의 범위에서 이루어지므로 JVM은 루프를 최적화하는 데 매우 효과적이지만 단순한 비교 및 반환 연산을 능가할 수는 없습니다. 최적화된 코드에는 폴백이 있으므로 코드 134에서 루프로 되돌아가는 것을 볼 수 있습니다. "원래" 실행에서 추가로 볼 수 있는 것은 루프를 반복하면 열거형에서 요소가 배치되는 위치도 중요하다는 것입니다. 0과 1은 거의 맨 위에 있는 반면 134는 맨 아래에 있습니다. 따라서 일반적인 경우의 순서를 다시 지정하는 것도 의미가 있습니다.
따라서 지금쯤이면 이러한 변화를 구현하는 것이 합리적이라고 확신할 수 있습니다. 할당량이 줄어들고 실행 속도가 빨라집니다. 또한 코드가 더 복잡해지지도 않습니다. 하지만 실제로는 이 두 가지로는 충분하지 않기 때문에 최적화된 경로에 더 많은 코드가 필요합니다. 긴 열거형 목록에서 보면 그렇습니다:
- 성공
- ERR_NOT_FOUND
- ERR_EXISTS
- err_not_my_vbucket
- ERR_BUSY
- ERR_TOO_BIG
- ERR_TEMP_FAIL
마지막 한 가지 질문에 대한 답이 남아 있습니다. if/elseif를 사용해야 할까요, 아니면 단순한 switch() 블록을 사용해야 할까요? 그게 전혀 중요할까요? JMH 벤치마크가 하나 더 있습니다:
결과는 다음과 같습니다:
이제 빠른 경로 코드에 세 가지 매개변수를 모두 포함했기 때문에 더 이상 이상값이 없습니다. 또한 사용 사례에서 실질적인 차이가 전혀 없다는 것을 알 수 있습니다. 따라서 case와 if/else 모두 잘 작동하며 선호도와 코드 명확성에 따라 선택할 수 있습니다.
오늘은 여기까지입니다! 무작정 추측하는 대신 교육적인 측정을 위해 JMH 및 JFR과 같은 도구를 사용하면 객체 할당을 줄이고 부수적으로 코드를 더 빠르게 만들 수 있습니다. 여러분 모두 즐거운 해킹과 경쟁 조건이 없기를 바랍니다.
좋은 글입니다!
첫 번째 시도는 값()의 결과를 KeyValueStatus2에 값으로 캐시하고 두 번째 단계로 일반적인 경우에 대한 열거형 값을 다시 정렬하는 것이었습니다.
In 내 결과 이것은 일반적인 경우에는 귀하의 버전보다 1-2ns 정도 늦지만, 엣지 케이스에서는 조금 더 낫습니다 - 아마도 추가 최적화 후보일 것입니다 ;-)
안녕하세요, 토마스,
좋은 아이디어입니다 - 피드백 감사합니다! 한 번 시도해 보겠습니다. 현장에서 얼마나 자주 나타나는지에 따라 재주문할 수도 있습니다.
멋지네요! 녹음본을 구할 수 있을까요? :)