OpenJDK, 40줄 수정으로 400배 성능 향상!
OpenJDK 커밋 로그 분석을 통해 40줄의 코드 삭제로 400배의 성능 향상을 이룬 사례를 발견함
기존의 /proc 기반 CPU 시간 측정 방식 대신 clock_gettime() 함수를 사용하여 성능 병목을 해결함
clock_gettime() 사용으로 시스템 콜(System Call) 횟수 감소 및 커널 락(Kernel Lock) 경쟁 문제를 해결함
추가적으로, clockid를 직접 구성하여 커널의 빠른 경로(Fast Path)를 활용하는 최적화 기법을 제시함
/proc 기반 CPU 시간 측정 방식의 문제점
기존 OpenJDK는 스레드(Thread)의 CPU 사용 시간을 측정하기 위해 `/proc` 파일 시스템을 사용했다. 이 방식은 파일 I/O(File I/O), 문자열 파싱(String Parsing), 그리고 여러 번의 시스템 콜(System Call)을 필요로 하여 성능 저하의 원인이 되었다. 특히, 경쟁 조건(Race Condition) 하에서는 커널 자원(Kernel Resource)에 대한 경합으로 인해 성능 저하가 더욱 심화되었다.
clock_gettime()을 활용한 성능 개선
새로운 구현은 `clock_gettime()` 함수를 사용하여 CPU 시간을 측정한다. 이 방식은 단일 시스템 콜만 호출하며, 복잡한 파싱 과정이 필요 없다. POSIX 표준(POSIX Standard)을 준수하면서도, Linux 커널의 특정 기능을 활용하여 사용자 시간(User Time)만을 정확하게 측정할 수 있다. 이러한 변화는 400배에 달하는 성능 향상을 가져왔다.
Linux 커널의 clockid 비트 해킹(Clockid Bit Hack)
Linux 커널은 `clockid_t` 값에 클럭 유형 정보를 인코딩한다. 이를 활용하여, `pthread_getcpuclockid()`를 통해 얻은 clockid의 특정 비트를 조작함으로써 사용자 시간(User Time)만 측정하도록 할 수 있다. 이 기술은 POSIX 표준을 우회하면서도 Linux 시스템의 성능을 최적화하는 데 기여한다. 데이터 미저장 정책(Zero-Retention Policy)을 구현하는 데도 활용될 수 있다.
추가적인 최적화: PID=0을 활용한 빠른 경로(Fast Path)
저자는 `pthread_getcpuclockid()` 대신, clockid를 직접 구성하는 방식을 제안한다. PID를 0으로 설정하면 커널은 radix tree lookup 과정을 생략하고, 현재 스레드의 정보를 직접 참조하는 빠른 경로(Fast Path)를 사용한다. 이로 인해, 평균 13%의 성능 향상을 추가적으로 얻을 수 있다. 하지만, 이는 커널 내부 구조에 대한 추가적인 의존성을 갖는다는 트레이드오프(Trade-offs)를 수반한다.
성능 벤치마크 결과
수정 전후의 성능을 비교하기 위해 JMH 벤치마크를 사용했다. 16개의 스레드를 사용한 결과, 평균 실행 시간이 11 마이크로초에서 279 나노초로 감소했다. 또한, clockid를 직접 구성하는 방식을 적용했을 때, 평균 실행 시간이 70.8 나노초로 추가 개선되었다. 이러한 결과는 clock_gettime()의 효율성과 커널 내부 구조에 대한 이해를 바탕으로 한 최적화의 중요성을 보여준다.