- OpenJDK'de
ThreadMXBean.getCurrentThreadUserTime(), /proc dosyası ayrıştırması yerine clock_gettime() çağrısı ile değiştirilerek en fazla 400 kat performans artışı sağladı
- Önceki uygulama,
/proc/self/task/<tid>/stat dosyasını açıp okuyup ayrıştıran karmaşık bir I/O yolundan geçiyordu
- Yeni uygulama, Linux çekirdeğinin
clockid_t bit kodlamasını kullanarak pthread_getcpuclockid() ile alınan kimliğin alt bitlerini ayarlıyor ve yalnızca user time'ı doğrudan sorguluyor
- Benchmark sonuçlarına göre ortalama çağrı süresi 11μs → 279ns seviyesine indi; ardından çekirdek fast-path uygulanınca yaklaşık %13 ek iyileşme görüldü
- Bu, POSIX kısıtlarının ötesine geçip Linux iç ABI'sini anlayarak optimizasyon yapılabileceğini gösteren bir örnek
Mevcut uygulamanın sorunu
getCurrentThreadUserTime(), CPU user time'ı hesaplamak için /proc/self/task/<tid>/stat dosyasını açıp 13. ve 14. alanları ayrıştırıyordu
- Dosya yolu oluşturma, dosya açma, tampon okuma, dize ayrıştırma,
sscanf() çağrısı gibi çok aşamalı işlemler gerekiyordu
- Komut adında parantez bulunabildiği için son
) karakterini strrchr() ile arayan karmaşık bir mantık da vardı
- Buna karşılık
getCurrentThreadCpuTime(), yalnızca tek bir clock_gettime(CLOCK_THREAD_CPUTIME_ID) çağrısı yapıyordu
- 2018 tarihli hata raporuna (JDK-8210452) göre iki metod arasındaki hız farkı 30 ila 400 kat arasındaydı
/proc erişim yolu ile clock_gettime() yolunun karşılaştırması
/proc yöntemi; open(), read(), sscanf(), close() gibi birden fazla sistem çağrısı ve çekirdek içinde dize oluşturma işlemleri içeriyor
clock_gettime() yöntemi ise tek bir sistem çağrısı ile sched_entity yapısından zaman değerini doğrudan okuyor
- Paralel yük altında
/proc erişimi, çekirdek kilidi rekabeti nedeniyle daha da fazla gecikmeye uğruyor
Yeni uygulama yöntemi
- POSIX standardı,
CLOCK_THREAD_CPUTIME_ID değerinin user+system time döndürmesini tanımlar
- Linux çekirdeği,
clockid_t alt bitleriyle saat türünü kodlar
00=PROF, 01=VIRT(yalnızca user), 10=SCHED(user+system)
pthread_getcpuclockid() ile alınan clockid değerinin alt bitlerini 01 olarak değiştirince yalnızca user time için saat elde edilebiliyor
- Yeni kod, dosya I/O'sunu ve ayrıştırmayı kaldırıp user time'ı yalnızca
clock_gettime() çağrısıyla döndürüyor
Performans ölçüm sonuçları
- Değişiklikten önce ortalama çağrı süresi 11.186μs, değişiklikten sonra 0.279μs oldu; yani yaklaşık 40 kat iyileşme sağlandı
- Ölçüm 16 thread ortamında yapıldı ve bu sonuç, başlangıçta bildirilen 30–400 kat aralığıyla uyumlu
- CPU profilinde dosya açma-kapama ile ilgili sistem çağrıları ortadan kalktı ve geriye yalnızca tek bir
clock_gettime() çağrısı kaldı
Çekirdek fast-path ile ek optimizasyon
- Çekirdek,
clockid içine PID=0 kodlandığında mevcut thread'e doğrudan erişen bir fast-path sağlıyor
- JVM,
pthread_getcpuclockid() yerine clockid değerini doğrudan oluşturup PID=0 koyarsa radix tree aramasını atlayabiliyor
- Elle oluşturulmuş
clockid kullanıldığında ortalama süre 81.7ns → 70.8ns oldu; bu da yaklaşık %13 ek iyileşme anlamına geliyor
- Ancak bu yaklaşım,
clockid_t boyutu gibi çekirdek içi uygulama ayrıntılarına bağlı olduğundan okunabilirlik ve uyumluluk kaybı riski taşıyor
Sonuç ve çıkarımlar
- 40 satırın silinmesiyle 400 katlık performans farkı ortadan kalktı; bu, yeni bir çekirdek özelliği eklemeden, yalnızca mevcut ABI'nin ayrıntılı yapısından yararlanılarak başarıldı
- Çekirdek kaynak kodunu incelemenin değeri vurgulanıyor: POSIX taşınabilirliği garanti eder, ancak çekirdek kodu nelerin mümkün olduğunun sınırlarını gösterir
- Mevcut varsayımları yeniden gözden geçirmenin önemi ortaya çıkıyor:
/proc ayrıştırması geçmişte makul olabilirken bugün verimsiz kalıyor
- Bu değişiklik JDK 26'ya (Mart 2026'da yayımlanması planlanıyor) dahil edilecek ve
ThreadMXBean.getCurrentThreadUserTime() çağrılarında otomatik performans artışı sağlayacak
5 yorum
Müthiş.
Bunun tamamen yanlış bir söz olduğunu düşünmüyorum ama işin içinde kernel varsa, yavaş olduğunu fark etmek bile gerçekten çok zor olmuştur diye düşünüyorum.
Bunlar projelerde nasıl tespit edilebilir? Sadece yapay zeka çalıştırarak bunu anlamak zor gibi görünüyor..
Böyle örnekleri görünce ben de öğrenip bunu mutlaka bizzat deneyimlemek istiyorum.
Aslında kod tabanının tamamını baştan yazıp 2-3 kat iyileşme sağlamak bile zor bir iş; buna karşılık sadece birkaç satırı değiştirerek en fazla 400 kat hızlanma elde etmek gerçekten etkileyici.
Hacker News yorumları
“Bu thread’in CPU kullanım süresi ne kadar?” sorusunun beklediğimden çok daha pahalı bir işlem olduğunu öğrendim
Atom saati seviyesinde bir referans yoksa mutlak sayılar iddia etmenin zor olduğunu düşünüyorum
clock_gettime()vDSO üzerinden çalışarak context switch’i önlüyor. Bu yüzden flamegraph’ta da izi görünüyorCLOCK_VIRTya daCLOCK_SCHEDgibi durumlarda hâlâ syscall çağrısı gerekiyorCLOCK_THREAD_CPUTIME_IDeninde sonunda kernel’e gidiyor. Çünkü task struct’a bakması gerekiyorİlgili kernel kaynakları: posix-cpu-timers.c,
cputime.c,
gettimeofday.c
PERF_COUNT_SW_TASK_CLOCKkullanılırsa yaklaşık 8ns seviyesinde ölçüm de yapılabiliyorperf_event_mmap_pageüzerinden paylaşılan sayfadan okunup, deltardtscçağrısıyla hesaplanıyorPek iyi belgelenmemiş ve açık kaynak implementasyonu da neredeyse yok
perf_eventkurulumu ve yetki gereksinimleri yüksek olduğu için uzun ömürlü thread’ler için daha uygun görünüyorseqlockneden gerekli diye sorulmuş. Sayfa değeri ilerdtscarasında context switch olmamasını sağlamak için mi diye merak ediyorMuhtemelen
rdtscsonrasında sayfa değeri tekrar kontrol edilip değiştiyse yeniden deneniyorBu arada
clock_gettimeda vdso tabanlı sanal bir syscallclock_gettimebir syscall değil, vdso kullanıyorSadece koda bakınca her şey normal görünebiliyor ama flamegraph’a bakınca sık sık “Bu da ne?!” dedirtiyor
Statik olmayan başlatma, tek satırlık logger çağrısının pahalı serialization tetiklemesi gibi pek çok sorun buldum
Normalde async-profiler’ın HTML üreticisini kullanıyorum ama bu kez tek bir SVG için Brendan’ın aracını kullandım
/procokunurken oluşan bellek ek yükünü, eBPF profillemeyi ve iyi belgelenmemiş user-space ABI’nin geçmişini ele aldımAyrıntıları blog yazımda toparladım
Kaynak tweet
Jaromir’in blogu da gerçekten harikaydı