30 Punkte von GN⁺ 2026-01-15 | Noch keine Kommentare. | Auf WhatsApp teilen
  • In OpenJDK wurde ThreadMXBean.getCurrentThreadUserTime() durch einen clock_gettime()-Aufruf statt /proc-Dateiparsing ersetzt und erreicht damit eine bis zu 400-fache Performance-Steigerung
  • Die bisherige Implementierung durchlief einen komplexen I/O-Pfad, bei dem die Datei /proc/self/task/<tid>/stat geöffnet, gelesen und geparst wurde
  • Die neue Implementierung nutzt die Bit-Kodierung von clockid_t im Linux-Kernel: Durch Anpassen der unteren Bits einer per pthread_getcpuclockid() erhaltenen ID kann direkt nur die User-Zeit abgefragt werden
  • Laut Benchmark sank die durchschnittliche Aufrufzeit von 11μs auf 279ns; nach Anwendung eines Kernel-Fast-Path folgte eine zusätzliche Verbesserung um etwa 13 %
  • Das ist ein Beispiel dafür, wie durch Verständnis der internen Linux-ABI über POSIX-Einschränkungen hinaus Optimierungen möglich werden

Probleme der bisherigen Implementierung

  • getCurrentThreadUserTime() öffnete die Datei /proc/self/task/<tid>/stat und parste das 13. und 14. Feld, um die CPU-User-Zeit zu berechnen
    • Dafür waren mehrere Verarbeitungsschritte nötig: Pfad erzeugen, Datei öffnen, Buffer lesen, String parsen, sscanf() aufrufen
    • Weil der Befehlsname Klammern enthalten kann, war zudem komplizierte Logik mit strrchr() nötig, um das letzte ) zu finden
  • getCurrentThreadCpuTime() führte dagegen nur einen einzelnen Aufruf von clock_gettime(CLOCK_THREAD_CPUTIME_ID) aus
  • Laut einem Bug-Report aus dem Jahr 2018 (JDK-8210452) betrug der Geschwindigkeitsunterschied zwischen den beiden Methoden 30- bis 400-fach

Vergleich von /proc-Zugriffspfad und clock_gettime()-Pfad

  • Der /proc-Ansatz umfasst mehrere Systemaufrufe und interne String-Erzeugung im Kernel, darunter open(), read(), sscanf() und close()
  • Der clock_gettime()-Ansatz liest den Zeitwert per einzelnem Systemaufruf direkt aus der Struktur sched_entity
  • Unter paralleler Last verstärkt sich die Verzögerung beim /proc-Zugriff durch Lock-Contention im Kernel

Neue Implementierung

  • Der POSIX-Standard definiert, dass CLOCK_THREAD_CPUTIME_ID User- plus Systemzeit zurückgibt
  • Der Linux-Kernel kodiert den Typ der Uhr in den unteren Bits von clockid_t
    • 00=PROF, 01=VIRT (nur User-Zeit), 10=SCHED (User+System)
  • Wenn bei einer mit pthread_getcpuclockid() erhaltenen clockid die unteren Bits auf 01 gesetzt werden, lässt sich auf eine nur für User-Zeit zuständige Uhr umschalten
  • Im neuen Code entfallen Datei-I/O und Parsing; die User-Zeit wird nur noch über clock_gettime() zurückgegeben

Ergebnisse der Performance-Messung

  • Vor der Änderung lag die durchschnittliche Aufrufzeit bei 11,186μs, danach bei 0,279μs - eine Verbesserung um etwa das 40-Fache
    • Gemessen in einer Umgebung mit 16 Threads, im Einklang mit der ursprünglich gemeldeten Spanne von 30- bis 400-fach
  • Im CPU-Profil verschwanden die Systemaufrufe zum Öffnen und Schließen von Dateien; übrig blieb nur ein einzelner clock_gettime()-Aufruf

Zusätzliche Optimierung durch Kernel-Fast-Path

  • Der Kernel bietet einen Fast-Path, wenn in clockid PID=0 kodiert ist und damit direkt auf den aktuellen Thread zugegriffen werden kann
  • Wenn die JVM die clockid direkt konstruiert statt pthread_getcpuclockid() zu verwenden und dabei PID=0 einsetzt, kann die Radix-Tree-Suche übersprungen werden
  • Bei Verwendung einer manuell konstruierten clockid sank die durchschnittliche Zeit von 81,7ns auf 70,8ns, also um weitere etwa 13 %
  • Allerdings besteht die Gefahr von Einbußen bei Lesbarkeit und Kompatibilität, da dies von Kernel-internen Implementierungsdetails wie der Größe von clockid_t abhängt

Fazit und Lehren

  • Durch das Löschen von 40 Zeilen wurde ein 400-facher Performance-Unterschied beseitigt, ganz ohne neue Kernel-Funktionalität, allein durch Nutzung von Details der bestehenden ABI
  • Hervorgehoben wird der Wert des Studiums des Kernel-Quellcodes: POSIX garantiert Portabilität, aber der Kernel-Code zeigt die Grenzen des Möglichen
  • Wichtig ist auch, bestehende Annahmen neu zu prüfen: Das Parsen von /proc war früher sinnvoll, ist heute aber ineffizient
  • Die Änderung wird in JDK 26 enthalten sein, das voraussichtlich im März 2026 erscheint, und sorgt damit bei Aufrufen von ThreadMXBean.getCurrentThreadUserTime() automatisch für bessere Performance

Noch keine Kommentare.

Noch keine Kommentare.