Une LinkedList sera-t-elle plus rapide ? Dois-je échanger le « for each » avec un « itérateur » ? Cette « ArrayList » devrait-elle être un « Tableau » ? Cet article est né en réponse à une optimisation si malveillante qu’elle est restée gravée pour toujours dans ma mémoire.
Avant d'aborder Java et les moyens de lutter contre les interférences, provenant soit du ramasse-miettes, soit du changement de contexte, jetons d'abord un coup d'œil sur les principes fondamentaux de l'écriture de code pour votre futur.
L'optimisation prématurée est la racine de tout Mal.
Vous l'avez déjà entendu ; L'optimisation prématurée est la racine de tout Mal. Eh bien, parfois. Lorsque j'écris des logiciels, je crois fermement qu'il faut être :
aussi descriptif que possible ; vous devriez essayer de raconter vos intentions comme si vous écriviez une histoire.
aussi optimal que possible ; ce qui signifie que vous devez connaître les bases de la langue et les appliquer en conséquence.
Votre code doit exprimer une intention, et cela dépend en grande partie de la façon dont vous nommez les méthodes et les variables.
int[10] array1; // bad int[10] numItems; // better int[10] backPackItems; // great
Rien que par le nom de la variable, vous pouvez déjà déduire la fonctionnalité.
Bien que numItems
soit abstrait, backPackItems
vous en dit long sur le comportement attendu.
Ou disons que vous avez cette méthode :
List<Countries> visitedCountries() { if(noCountryVisitedYet) return new ArrayList<>(0); } // (...) return listOfVisitedCountries; }
En ce qui concerne le code, cela semble plus ou moins correct.
Pouvons-nous faire mieux ? Nous pouvons certainement le faire !
List<Countries> visitedCountries() { if(noCountryVisitedYet) return Collections.emptyList(); } // (...) return listOfVisitedCountries; }
La lecture Collections.emptyList()
est beaucoup plus descriptive que new ArrayList<>(0);
Imaginez que vous lisez le code ci-dessus pour la première fois et que vous tombiez sur la clause de garde qui vérifie si l'utilisateur a réellement visité des pays. Imaginez également que cela soit enterré dans une longue classe, la lecture Collections.emptyList()
est nettement plus descriptive que new ArrayList<>(0)
, vous vous assurez également qu'il est immuable en vous assurant que le code client ne peut pas le modifier.
Connaissez votre langue et utilisez-la en conséquence. Si vous avez besoin d'un double
, il n'est pas nécessaire de l'envelopper dans un objet Double
. Il en va de même pour l’utilisation d’un List
si tout ce dont vous avez réellement besoin est un Array
.
Sachez que vous devez concaténer des chaînes à l'aide StringBuilder
ou StringBuffer
si vous partagez l'état entre les threads :
// don't do this String votesByCounty = ""; for (County county : counties) { votesByCounty += county.toString(); } // do this instead StringBuilder votesByCounty = new StringBuilder(); for (County county : counties) { votesByCounty.append(county.toString()); }
Sachez comment indexer votre base de données. Anticipez les goulots d’étranglement et mettez en cache en conséquence. Tout ce qui précède sont des optimisations. C’est le genre d’optimisations dont vous devez être conscient et mettre en œuvre en tant que premiers citoyens.
Je n'oublierai jamais un hack que j'ai lu il y a quelques années. À vrai dire, l’auteur a rapidement fait marche arrière, mais cela montre à quel point de bonnes intentions peuvent susciter beaucoup de mal.
// do not do this, ever! int i = 0; while (i<10000000) { // business logic if (i % 3000 == 0) { //prevent long gc try { Thread.sleep(0); } catch (Ignored e) { } } }
Un hack d'éboueur de l'enfer !
Vous pouvez en savoir plus sur pourquoi et comment le code ci-dessus fonctionne dans l'article original et, bien que l'exploit soit certainement intéressant, c'est l'une de ces choses que vous ne devriez jamais faire.
Thread.sleep(0)
n'a aucun but dans ce bloc
Ne commencez à forger quelque chose d'un peu plus complexe que si, après avoir écrit avec toutes les optimisations par défaut fournies par le langage , vous rencontrez un goulot d'étranglement. Mais évitez les concoctions comme ci-dessus.
Si après tout, le Garbage Collector est toujours la pièce qui offre de la résistance, voici quelques-unes des choses que vous pouvez essayer :
Si votre service est si sensible à la latence que vous ne pouvez pas autoriser le GC, exécutez avec "Epsilon GC" et évitez complètement le GC .
-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
Cela augmentera évidemment votre mémoire jusqu'à ce que vous obteniez une exception MOO, donc soit c'est un scénario de courte durée, soit votre programme est optimisé pour ne pas créer d'objets.
Si votre service est quelque peu sensible à la latence, mais que la tolérance autorisée permet une certaine marge de manœuvre , exécutez GC1 et alimentez-le quelque chose comme -XX:MaxGCPauseTimeMillis=100
(la valeur par défaut est 250 ms)
Si le problème provient de bibliothèques externes , disons que l'une d'entre elles appelle System.gc()
ou Runtime.getRuntime().gc()
qui sont des éboueurs stop-the-world, vous pouvez remplacer le comportement incriminé en exécutant avec -XX:+DisableExplicitGC
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
. Vous pouvez également consulter ce benchmark JDK 21 GC .
DÉBUT DE LA VERSION | FIN DE LA VERSION | CG PAR DÉFAUT |
---|---|---|
Java1 | Java4 | Collecteur de déchets en série |
Java5 | Java8 | Collecteur de déchets parallèle |
Java9 | en cours | Collecteur de déchets G1 |
Remarque 1 : depuis Java 15, ZGC
est prêt pour la production , mais vous devez toujours l'activer explicitement avec -XX:+UseZGC
.
Remarque 2 : La VM considère les machines comme étant de classe serveur si la VM détecte plus de deux processeurs et une taille de segment supérieure ou égale à 1 792 Mo. S'il ne s'agit pas d'une classe serveur, il s'agira par défaut du Serial GC .
Essentiellement, optez pour le réglage GC lorsqu'il est clair que les contraintes de performances de l'application sont directement liées au comportement du garbage collection et que vous disposez de l'expertise nécessaire pour effectuer des ajustements éclairés. Sinon, faites confiance aux paramètres par défaut de la JVM et concentrez-vous sur l'optimisation du code au niveau de l'application.
u/shiphe - vous voudrez lire le commentaire complet
Si vous optimisez par ressenti sans véritable analyse comparative, vous ne vous rendez pas service. JMH est la bibliothèque Java de facto pour tester les performances de vos algorithmes. Utilise le.
Épingler un processus sur un cœur spécifique peut améliorer les accès au cache. Cela dépendra du matériel sous-jacent et de la manière dont votre routine traite les données. Néanmoins, cette bibliothèque la rend si facile à implémenter que, si une méthode gourmande en CPU vous traîne, vous aurez envie de la tester.
C'est une de ces bibliothèques que, même si vous n'en avez pas besoin, vous aurez envie d'étudier. L’idée est de permettre une concurrence à latence ultra faible. Mais la manière dont il est mis en œuvre, de la sympathie mécanique au tampon annulaire, apporte de nombreux nouveaux concepts. Je me souviens encore de la première fois que je l'ai découvert, il y a sept ans, après avoir passé une nuit blanche à le digérer.
Le principe de jvmquake
est que lorsque les choses tournent mal avec la JVM, vous voulez qu'elle meure et ne se bloque pas. Il y a quelques années, j'exécutais des simulations sur un cluster HTCondor soumis à des contraintes de mémoire strictes, et parfois, les tâches restaient bloquées en raison d'erreurs de « mémoire insuffisante ».
Cette bibliothèque force la JVM à mourir, vous permettant de gérer l'erreur réelle. Dans ce cas précis, HTCondor replanifierait automatiquement le travail.
Le code qui m'a fait écrire ce post ? J'ai écrit bien pire. Je le fais encore. Le mieux que nous puissions espérer est de continuer à faire moins de erreurs.
Je m'attends à être mécontent en regardant mon propre code dans quelques années.
Et c'est bon signe.
visitedCountries()
et l'explication détaillée.Également publié sur wasteofserver.com