ThreadMXBean.getCurrentThreadUserTime() di OpenJDK diganti dari parsing file /proc menjadi pemanggilan clock_gettime(), menghasilkan peningkatan performa hingga 400x
- Implementasi lama melewati jalur I/O yang kompleks dengan membuka, membaca, dan mem-parsing file
/proc/self/task/<tid>/stat
- Implementasi baru memanfaatkan enkode bit
clockid_t di kernel Linux dengan menyesuaikan bit rendah dari ID yang diperoleh lewat pthread_getcpuclockid(), sehingga hanya waktu user yang bisa diambil langsung
- Hasil benchmark menunjukkan waktu panggilan rata-rata turun dari 11μs → 279ns, lalu setelah fast-path kernel diterapkan ada peningkatan tambahan sekitar 13%
- Ini menjadi contoh bahwa optimasi dimungkinkan dengan memahami ABI internal Linux melampaui batasan POSIX
Masalah pada implementasi lama
getCurrentThreadUserTime() menghitung CPU user time dengan membuka file /proc/self/task/<tid>/stat lalu mem-parsing field ke-13 dan ke-14
- Dibutuhkan beberapa tahap seperti membuat path file, membuka file, membaca buffer, mem-parsing string, hingga memanggil
sscanf()
- Karena nama perintah bisa mengandung tanda kurung, ada juga logika rumit untuk mencari
) terakhir dengan strrchr()
- Sebaliknya,
getCurrentThreadCpuTime() hanya melakukan satu pemanggilan clock_gettime(CLOCK_THREAD_CPUTIME_ID)
- Menurut laporan bug 2018 (JDK-8210452), selisih kecepatan antara dua metode ini mencapai 30~400x
Perbandingan jalur akses /proc dan jalur clock_gettime()
- Metode
/proc mencakup beberapa system call dan pembuatan string internal di kernel seperti open(), read(), sscanf(), dan close()
- Metode
clock_gettime() membaca nilai waktu langsung dari struktur sched_entity lewat satu system call
- Saat ada beban paralel, akses
/proc mengalami latensi yang makin besar karena kontensi lock di kernel
Cara kerja implementasi baru
- Standar POSIX mendefinisikan bahwa
CLOCK_THREAD_CPUTIME_ID mengembalikan waktu user + sistem
- Kernel Linux mengenkode jenis clock di bit-bit rendah
clockid_t
00=PROF, 01=VIRT(hanya user), 10=SCHED(user+sistem)
- Jika bit rendah
clockid yang didapat dari pthread_getcpuclockid() diubah menjadi 01, clock tersebut bisa diubah menjadi clock khusus user time
- Kode baru menghapus file I/O dan parsing, lalu cukup mengembalikan user time melalui pemanggilan
clock_gettime() saja
Hasil pengukuran performa
- Sebelum perubahan, rata-rata waktu panggilan adalah 11.186μs, setelah perubahan menjadi 0.279μs, atau sekitar 40x lebih cepat
- Diukur pada lingkungan 16 thread, konsisten dengan rentang 30~400x yang sebelumnya dilaporkan
- Pada profil CPU, system call terkait buka-tutup file menghilang, dan hanya tersisa satu pemanggilan
clock_gettime()
Optimasi tambahan dengan kernel fast-path
- Kernel menyediakan fast-path yang langsung mengakses thread saat
PID=0 dienkodekan di clockid
- Jika JVM menyusun
clockid sendiri alih-alih memakai pthread_getcpuclockid(), lalu memasukkan PID=0, maka pencarian radix tree bisa dilewati
- Saat memakai
clockid yang disusun manual, waktu rata-rata turun dari 81.7ns → 70.8ns, atau sekitar 13% lebih cepat lagi
- Namun pendekatan ini bergantung pada implementasi internal kernel seperti ukuran
clockid_t, sehingga ada risiko mengorbankan keterbacaan dan kompatibilitas
Kesimpulan dan pelajaran
- Penghapusan 40 baris kode berhasil menghilangkan kesenjangan performa 400x, tanpa fitur kernel baru, hanya dengan memanfaatkan detail struktur ABI yang sudah ada
- Kasus ini menegaskan nilai dari membaca source code kernel: POSIX menjamin portabilitas, tetapi kode kernel menunjukkan batas dari apa yang mungkin dilakukan
- Ini juga menunjukkan pentingnya meninjau ulang asumsi lama: parsing
/proc dulu masuk akal, tetapi sekarang tidak efisien
- Perubahan ini akan masuk ke JDK 26 (dijadwalkan rilis pada Maret 2026), sehingga pemanggilan
ThreadMXBean.getCurrentThreadUserTime() akan mendapat peningkatan performa otomatis
5 komentar
Luar biasa sekali.
Menurut saya itu bukan perkataan yang sepenuhnya salah, tetapi dalam kasus yang terkait dengan kernel, saya rasa bahkan menyadari bahwa itu lambat pun pasti benar-benar sulit.
Hal-hal seperti ini biasanya ditemukan bagaimana di dalam proyek? Rasanya akan sulit untuk mengetahuinya hanya karena menjalankan AI..
Melihat kasus-kasus seperti ini membuat saya juga ingin belajar dan pasti ingin mengalaminya sendiri.
Sebenarnya, bahkan meningkatkan performa 2~3 kali lipat dengan merombak seluruh kode pun sudah sulit, jadi sungguh luar biasa bisa mencapai peningkatan hingga 400 kali lipat hanya dengan mengubah beberapa baris saja.
Opini Hacker News
Saya jadi sadar bahwa pertanyaan “berapa waktu CPU yang digunakan thread ini?” ternyata adalah operasi yang sangat mahal
Tanpa acuan setingkat jam atom, menurut saya sulit mengklaim angka absolut
clock_gettime()menghindari context switch melalui vDSO. Karena itu jejaknya juga terlihat di flamegraphCLOCK_VIRTatauCLOCK_SCHED, masih tetap perlu pemanggilan syscallCLOCK_THREAD_CPUTIME_IDpada akhirnya tetap masuk ke kernel karena harus merujuk task structUntuk sumber kernel terkait, lihat posix-cpu-timers.c,
cputime.c,
gettimeofday.c
PERF_COUNT_SW_TASK_CLOCK, pengukuran di kisaran sekitar 8ns juga dimungkinkanCaranya membaca dari shared page lewat
perf_event_mmap_page, lalu menghitung delta dengan pemanggilanrdtscHal ini tidak terdokumentasi dengan baik dan hampir tidak ada implementasi open source
perf_eventdan perizinannya cukup besar, sepertinya lebih cocok untuk thread yang berumur panjangseqlockdiperlukan. Apakah untuk memastikan tidak terjadi context switch antara nilai halaman danrdtscKemungkinan strukturnya adalah memeriksa lagi nilai halaman setelah
rdtsc, lalu mencoba ulang jika berubahSebagai catatan,
clock_gettimejuga merupakan virtual syscall berbasis vdsoclock_gettimebukan syscall, melainkan menggunakan vdsoSaat hanya melihat kode, semuanya tampak baik-baik saja, tetapi ketika melihat flamegraph sering muncul reaksi “ini apaan?!”
Saya pernah menemukan berbagai masalah seperti inisialisasi yang ternyata bukan statis, dan pemanggilan logger satu baris yang memicu serialisasi mahal
Biasanya saya memakai generator HTML dari async-profiler, tetapi kali ini saya menggunakan alat Brendan demi satu file SVG tunggal
/proc, profiling eBPF, dan sejarah user-space ABI yang dokumentasinya kurang memadaiDetail lebih lanjut saya rangkum di tulisan blog saya
Tweet sumber
Blog Jaromir juga sangat keren