• # Le but d’un GC n’a jamais été les performances

    Posté par (site web personnel, Mastodon) . En réponse au journal Performances et GC : détruisons les mythes. Évalué à 10.

    Dans la plupart des cas, l’intérêt d’avoir un GC dans le langage, c’est de ne pas avoir à gérer la mémoire. Et donc :

    • D’avoir un code plus lisible (puisqu’il n’y a pas à écrire de code pour la gestion manuelle de la mémoire).
    • D’éviter toutes les catégories de bugs et de failles de sécurité qui arrivent parce que la mémoire a (mal) été gérée à la main.

    Mais oui, ça se fait au dépend des performances : même dans le cas où le GC ne nettoie jamais rien, le simple fait de lui permettre de pouvoir passer un jour ajoute une surcharge de travail au système. C’est au point qu’on considère qu’en première approche, un GC doit passer le moins souvent possible – sauf si le passer rarement provoque un blocage complet du système si long que la solution n’est plus applicable.

    D’autre part, des techniques de programmation orienté sûreté ou fiabilité ont un impact négatif sur la pression mémoire. Par exemple les objets immutables ont beaucoup d’avantages, mais peuvent vite conduire à la création d’une grande quantité d’objets, qui devront être gérés par le GC même si leur vie est très courte (et donc n’utilisent pas significativement plus de mémoire que leur pendants mutables).

    En fait, si le GC devient un problème pour les performances, ça peut vouloir dire trois choses :

    1. Il y a vraiment pas assez de mémoire disponible et le GC passe son temps à nettoyer en boucle les quelques pourcents de mémoire qui peuvent l’être ;
    2. Soit le code est très mal branlé et surcharge inutilement le GC – généralement suite à une fuite mémoire, parce que trop souvent on confonds le GC avec de la magie ;
    3. Soit il y a un besoin réel de performances sur la mémoire que le langage ne peut pas résoudre sans y allouer une quantité déraisonnable de mémoire.

    Le point 1 se détecte facilement en espionnant l’utilisation mémoire et l’activité du GC pendant l’exécution du programme ; le point 2 en analysant un dump mémoire qui contient plein d’objets qui ne devrait pas y être. Ça ne veut pas dire que ces points sont faciles à corriger.

    Le point 3 peut très rarement se corriger avec des bidouilles de code ; en en particulier ne peut jamais se corriger efficacement avec des magouilles lourdes du type de celles présentées dans l’article, parce que dans ce cas on a le pire des deux mondes : un code difficile à comprendre (donc à maintenir et sujet à bugs) et la surcharge d’un GC. De plus, beaucoup de ces magouilles (au moins en Java, langage que je connais le mieux) sont au mieux obsolètes : le compilateur puis le GC arrivent très bien à comprendre ce qu’il faut faire sans ces indications, qui sont donc superflues et alourdissent le code pour rien.

    Enfin, les problèmes de GC sont très dépendants du contexte exact d’exécution. Sur ce point, j’ai énormément de mal à faire confiance à des microbenchmark.

    Je ne prétends pas avoir un métier représentatif, mais en 12 ans à faire du Java de façon professionnelle sur des sujets variés (y compris de l’Android), tous les problèmes de pression mémoire que j’ai rencontrés tombent dans les deux premiers cas. Je dirais trois quarts tiers de fuites mémoire et un quart de manque réel de mémoire dans le paramétrage, à la louche (souvent suite à des jeux de données irréaliste avant d’arriver en production). Et même au-delà de ça, les problèmes d’occupation mémoire que j’ai croisés sont plutôt rares, sauf sur les vieux Android : le facteur limitant des programmes que j’ai croisés sont généralement les I/O1 , ou des grosses erreurs de programmation (ne me faites pas dire ce que je ne dis pas : bien sûr qu’on aurait pu faire plus rapide avec d’autres langages, mais sur les cas que j’ai croisé, une fois les erreurs de programmation corrigées, on a toujours été suffisamment rapides).

    En conclusion, lorsqu’on utilise un langage à GC, on devrait s’astreindre à conserver un code le plus lisible possible, et à ne faire des bizarreries pour gérer la mémoire que de façon très exceptionnelle et dans des contextes où il a été prouvé, en conditions réelles, que l’impact vaut la perte de lisibilité.


    1. Exception : un progicel IBM – maintenant revendu – qui était programmé en allant tellement loin dans le genre Serious Enterprise Programing caricatural qu’il arrivait à être limité par le CPU. Mais le code était vraiment horrible pour en arriver là.

    La connaissance libre : https://zestedesavoir.com