30 poin oleh GN⁺ 2026-01-15 | 5 komentar | Bagikan ke WhatsApp
  • 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

 
crawler 2026-01-15

Luar biasa sekali.

Kalau jadi 2 kali lebih cepat, mungkin itu karena melakukan sesuatu yang cerdas, dan kalau jadi 100 kali lebih cepat, itu cuma karena berhenti melakukan hal yang bodoh

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.

 
[Komentar ini disembunyikan.]
 
princox 2026-01-19

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.

 
aobamisaki 2026-01-15

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.

 
GN⁺ 2026-01-15
Opini Hacker News
  • Saya penulisnya. Setelah tulisan sebelumnya tentang bug kernel, saya menelusuri cara JVM melaporkan aktivitas threadnya sendiri
    Saya jadi sadar bahwa pertanyaan “berapa waktu CPU yang digunakan thread ini?” ternyata adalah operasi yang sangat mahal
    • Jika ingin membahas pengukuran pada skala nanodetik, kita harus benar-benar memahami stabilitas dan akurasi clock
      Tanpa acuan setingkat jam atom, menurut saya sulit mengklaim angka absolut
    • Saya penasaran apakah sudah diteliti mengapa distribusinya tersebar di beberapa orde besaran. Itu sendiri fenomena yang menarik
    • Saya sangat berterima kasih atas ringkasan TL;DR yang singkat. Ringkasan seperti ini menurunkan hambatan untuk mulai membaca dan memberi motivasi untuk lanjut
    • Ada yang menanggapi dengan “tidak mengejutkan (Quelle Surprise)”
  • clock_gettime() menghindari context switch melalui vDSO. Karena itu jejaknya juga terlihat di flamegraph
    • Namun ini hanya berlaku untuk sebagian clock. Untuk kasus seperti CLOCK_VIRT atau CLOCK_SCHED, masih tetap perlu pemanggilan syscall
    • Jika melihat ke bawah frame vDSO, syscall-nya masih ada. Tampaknya jalur cepat (fast path) untuk clock id tertentu belum diimplementasikan
    • CLOCK_THREAD_CPUTIME_ID pada akhirnya tetap masuk ke kernel karena harus merujuk task struct
      Untuk sumber kernel terkait, lihat posix-cpu-timers.c,
      cputime.c,
      gettimeofday.c
  • Dengan memakai PERF_COUNT_SW_TASK_CLOCK, pengukuran di kisaran sekitar 8ns juga dimungkinkan
    Caranya membaca dari shared page lewat perf_event_mmap_page, lalu menghitung delta dengan pemanggilan rdtsc
    Hal ini tidak terdokumentasi dengan baik dan hampir tidak ada implementasi open source
    • Ini trik yang sangat keren. Hanya saja, karena kebutuhan konfigurasi perf_event dan perizinannya cukup besar, sepertinya lebih cocok untuk thread yang berumur panjang
    • Ada yang bertanya mengapa seqlock diperlukan. Apakah untuk memastikan tidak terjadi context switch antara nilai halaman dan rdtsc
      Kemungkinan strukturnya adalah memeriksa lagi nilai halaman setelah rdtsc, lalu mencoba ulang jika berubah
      Sebagai catatan, clock_gettime juga merupakan virtual syscall berbasis vdso
    • clock_gettime bukan syscall, melainkan menggunakan vdso
  • Flamegraph benar-benar alat yang luar biasa
    Saat 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
    • Saya juga suka icicle graph. Ia mengakumulasi ke arah berlawanan dari flamegraph, sehingga lebih mudah melihat bottleneck ketika banyak jalur memanggil library bersama
    • Jika membuka contoh SVG ini di tab baru, Anda bisa melakukan zoom interaktif
    • Eksperimen profiling performa dan optimisasi adalah salah satu bagian paling menyenangkan dalam pengembangan. Sering ada kejutan seperti, “kok ini bisa selambat itu?”
    • Ada juga yang berpendapat bahwa kombinasi parsing string dan memoization terdengar aneh. Dalam praktiknya, masalahnya muncul karena parsing pola regex yang mahal tidak di-cache
    • Untuk orang yang baru ingin mencoba flamegraph, ada yang bertanya soal konsep dasar dan titik awal untuk memulai
  • Mengejutkan bahwa “buka gambar di tab baru” benar-benar memberikan interaksi SVG
    • Fitur ini berkat skrip FlameGraph milik Brendan Gregg
      Biasanya saya memakai generator HTML dari async-profiler, tetapi kali ini saya menggunakan alat Brendan demi satu file SVG tunggal
  • Saya penulis patch OpenJDK tersebut. Saya membahas overhead memori saat membaca /proc, profiling eBPF, dan sejarah user-space ABI yang dokumentasinya kurang memadai
    Detail lebih lanjut saya rangkum di tulisan blog saya
    • Ada pertanyaan mengapa implementasi awalnya dibuat seperti itu. Melakukan file I/O dan parsing string di setiap pemanggilan memang tidak efisien, tetapi saya rasa pasti ada alasannya pada masa itu
    • Jaromir melihat tulisan saya dan berkata, “saya juga menulis draf pada waktu yang hampir sama,” lalu saling menautkan tulisan masing-masing. Saya senang karena ia menilai tulisan saya lebih ketat secara teknis
  • Hanya karena memakai bahasa sistem seperti C atau C++, bukan berarti selalu cepat. Kecepatan sangat bergantung pada apa yang sebenarnya dilakukan
  • Pembacaan melalui vDSO jauh lebih cepat karena menghindari perpindahan ke kernel, serialisasi buffer, dan parsing
  • Ada yang membagikan kutipan, “jika jadi 2x lebih cepat, mungkin Anda melakukan sesuatu yang cerdas; jika jadi 100x lebih cepat, Anda hanya berhenti melakukan sesuatu yang bodoh
    Tweet sumber
  • Tim QuestDB berada di level tertinggi di bidang ini. Orang-orangnya hebat, dan perangkat lunaknya juga luar biasa
    Blog Jaromir juga sangat keren