OpenJDK, 40줄 수정으로 400배 성능 향상!
OpenJDK의 getCurrentThreadUserTime() 메서드 성능 개선을 위해 /proc 기반의 CPU 시간 측정 방식을 clock_gettime()으로 변경
기존 방식은 여러 시스템 콜(System Call)과 복잡한 파싱(Parsing)으로 인해 성능 저하를 유발했음
clock_gettime()을 사용한 결과, 평균 11마이크로초에서 279나노초로 40배 성능 향상
추가적으로 커널의 PID=0 고속 경로(Fast Path)를 활용하여 13% 추가 성능 향상 달성
/proc 기반 CPU 시간 측정의 문제점
기존 OpenJDK의 getCurrentThreadUserTime()은 `/proc/self/task/<tid>/stat` 파일을 읽어 CPU 시간을 측정했다. 이 방식은 `open()`, `read()`, `sscanf()` 등 여러 시스템 콜을 거치며, VFS(Virtual File System) 디스패치와 dentry lookup을 포함한다. 또한, 괄호를 포함한 명령 이름 파싱(Parsing) 문제로 인해 복잡한 처리가 필요했다. 이러한 과정은 성능 병목(Performance Bottleneck)을 유발하여, clock_gettime() 방식 대비 30~400배 느린 성능을 보였다.
clock_gettime()을 활용한 성능 개선
새로운 구현은 clock_gettime(CLOCK_THREAD_CPUTIME_ID)를 사용하여 스레드 CPU 시간을 측정한다. 이 방식은 단일 시스템 콜로, posix_cpu_clock_get()을 거쳐 cpu_clock_sample()에서 직접 스케줄링 정보를 읽어온다. 기존 `/proc` 방식과 비교하여, 파일 I/O, 복잡한 파싱, 버퍼 관리가 필요 없어 획기적인 성능 향상을 이끌어냈다. 40줄의 코드 삭제로 평균 40배의 성능 향상을 달성했다.
Linux 커널 내부의 최적화 기법
추가적인 성능 개선을 위해, 커널의 PID=0 고속 경로(Fast Path)를 활용하는 방법이 제시되었다. pthread_getcpuclockid()를 통해 얻은 clockid를 사용하는 대신, PID를 0으로 설정한 clockid를 직접 구성함으로써, 커널의 radix tree lookup 과정을 생략할 수 있다. 이로 인해 평균 13%의 추가 성능 향상을 얻었으며, 이는 커널 내부 구조(Kernel Internals)에 대한 깊이 있는 이해를 바탕으로 한 최적화이다.
POSIX 표준과 Linux 확장 기능
POSIX 표준은 CLOCK_THREAD_CPUTIME_ID가 전체 CPU 시간을 반환하도록 규정하고 있어, 사용자 시간만 얻는 데 제약이 있었다. 하지만, Linux 커널은 clockid_t 값에 스레드와 프로세스 정보를 인코딩하는 기능을 제공한다. 이를 통해, Linux-specific feature를 활용하여 사용자 시간만 측정하는 것이 가능해졌다. 이러한 Linux 확장 기능 활용은 OpenJDK의 성능 향상에 기여했다.