30 points par GN⁺ 2026-01-15 | Aucun commentaire pour le moment. | Partager sur WhatsApp
  • Dans OpenJDK, ThreadMXBean.getCurrentThreadUserTime() a été remplacé par un appel à clock_gettime() au lieu d’un parsing de fichier /proc, avec à la clé jusqu’à 400x de gain de performances
  • L’implémentation précédente suivait un chemin d’E/S complexe en ouvrant, lisant et analysant le fichier /proc/self/task/<tid>/stat
  • La nouvelle implémentation exploite l’encodage binaire de clockid_t dans le noyau Linux pour ajuster les bits de poids faible de l’identifiant obtenu via pthread_getcpuclockid() et interroger directement uniquement le temps utilisateur
  • Les benchmarks montrent que le temps moyen par appel passe de 11μs à 279ns, puis environ 13 % de mieux après l’application d’un fast-path côté noyau
  • C’est un exemple qui montre qu’une optimisation est possible au-delà des contraintes de POSIX grâce à une compréhension de l’ABI interne de Linux

Problèmes de l’implémentation existante

  • getCurrentThreadUserTime() ouvrait le fichier /proc/self/task/<tid>/stat pour analyser les 13e et 14e champs et calculer le temps CPU utilisateur
    • cela nécessitait plusieurs étapes : construction du chemin, ouverture du fichier, lecture dans un tampon, parsing de chaîne, appel à sscanf(), etc.
    • le nom de commande pouvant contenir des parenthèses, la logique incluait aussi une recherche complexe du dernier ) avec strrchr()
  • À l’inverse, getCurrentThreadCpuTime() n’effectuait qu’un seul appel à clock_gettime(CLOCK_THREAD_CPUTIME_ID)
  • D’après le rapport de bug de 2018 (JDK-8210452), l’écart de vitesse entre les deux méthodes atteignait 30 à 400x

Comparaison entre le chemin d’accès /proc et le chemin clock_gettime()

  • La méthode /proc inclut plusieurs appels système et la génération de chaînes dans le noyau, comme open(), read(), sscanf() et close()
  • La méthode clock_gettime() repose sur un seul appel système qui lit directement la valeur temporelle depuis la structure sched_entity
  • Sous charge parallèle, l’accès à /proc subit des ralentissements accrus à cause de la contention sur les verrous du noyau

Nouvelle méthode d’implémentation

  • Le standard POSIX définit que CLOCK_THREAD_CPUTIME_ID doit renvoyer le temps utilisateur + le temps système
  • Le noyau Linux encode le type d’horloge dans les bits de poids faible de clockid_t
    • 00=PROF, 01=VIRT(utilisateur uniquement), 10=SCHED(utilisateur + système)
  • En remplaçant les bits de poids faible du clockid obtenu via pthread_getcpuclockid() par 01, il devient possible de basculer vers une horloge dédiée uniquement au temps utilisateur
  • Le nouveau code supprime les E/S fichier et le parsing, et renvoie le temps utilisateur via un simple appel à clock_gettime()

Résultats des mesures de performances

  • Avant la modification, le temps moyen par appel était de 11,186μs ; après, il tombe à 0,279μs, soit une amélioration d’environ 40x
    • mesure réalisée dans un environnement à 16 threads, cohérente avec la fourchette initialement rapportée de 30 à 400x
  • Dans les profils CPU, les appels système liés à l’ouverture et à la fermeture de fichiers disparaissent, ne laissant qu’un seul appel à clock_gettime()

Optimisation supplémentaire avec le fast-path du noyau

  • Le noyau fournit un fast-path qui accède directement au thread courant lorsque le clockid encode PID=0
  • Si la JVM construit directement le clockid au lieu d’utiliser pthread_getcpuclockid(), en y injectant PID=0, elle peut éviter la recherche dans l’arbre radix
  • Avec un clockid construit manuellement, le temps moyen passe de 81,7ns à 70,8ns, soit environ 13 % d’amélioration supplémentaire
  • En contrepartie, cela dépend de détails d’implémentation internes du noyau, comme la taille de clockid_t, avec un risque de perte de lisibilité et de compatibilité

Conclusion et enseignements

  • La suppression de 40 lignes a éliminé un écart de performances de 400x, sans nouvelle fonctionnalité noyau, uniquement en exploitant la structure détaillée de l’ABI existante
  • L’article souligne la valeur d’une lecture attentive du code source du noyau : POSIX garantit la portabilité, mais le code du noyau montre les limites du possible
  • L’importance de reconsidérer les hypothèses existantes : analyser /proc était autrefois raisonnable, mais c’est aujourd’hui inefficace
  • Ce changement sera intégré à JDK 26 (sortie prévue en mars 2026) et apportera un gain de performances automatique lors des appels à ThreadMXBean.getCurrentThreadUserTime()

Aucun commentaire pour le moment.

Aucun commentaire pour le moment.