J'ai accidentellement découvert la définition de "Cochon dans le Python (Remarque : c'est un peu comme le serpent gourmand et insuffisant avalant l'éléphant)" en chinois en regardant le glossaire de gestion de la mémoire, j'ai donc trouvé cet article. En apparence, ce terme fait référence au GC qui fait constamment la promotion de gros objets d’une génération à l’autre. Faire cela, c'est comme un python avalant sa proie entière, de sorte qu'elle ne puisse pas bouger pendant qu'elle digère.
Pendant les 24 heures suivantes, mon esprit était rempli d'images de ce python suffocant dont je ne parvenais pas à me débarrasser. Comme le disent les psychiatres, la meilleure façon de soulager la peur est d’en parler. D'où cet article. Mais la prochaine histoire dont nous voulons parler n'est pas celle de Python, mais du réglage GC. Je le jure devant Dieu.
Tout le monde sait que les pauses du GC peuvent facilement provoquer des goulots d'étranglement dans les performances. Les JVM modernes sont livrées avec des garbage collectors avancés lors de leur sortie, mais d'après mon expérience, il est extrêmement difficile de trouver la configuration optimale pour une application donnée. Le réglage manuel a peut-être encore une lueur d'espoir, mais vous devez comprendre les mécanismes exacts de l'algorithme GC. À cet égard, cet article vous sera utile. Ci-dessous, j'utiliserai un exemple pour expliquer comment un petit changement dans la configuration de la JVM affecte le débit de votre application.
Exemple
L'application que nous avons utilisée pour démontrer l'impact de la GC sur le débit était un programme simple. Il contient deux fils de discussion :
PigEater Il imitera le processus d'un python géant mangeant un gros cochon gras. Le code fait cela en ajoutant 32 Mo d'octets à java.util.List et en dormant 100 ms après chaque hirondelle.
PigDgester Il simule le processus de digestion asynchrone. Le code qui implémente la digestion définit simplement la liste des porcs comme vide. Puisqu'il s'agit d'un processus fatigant, ce thread sera mis en veille pendant 2 000 ms à chaque fois après avoir effacé la référence.
Les deux threads fonctionneront dans une boucle while, mangeant et digérant jusqu'à ce que le serpent soit plein. Cela nécessiterait de manger environ 5 000 porcs.
Copiez le code comme suit :
paquet eu.plumbr.demo ;
classe publique PigInThePython {
Liste volatile statique pigs = new ArrayList();
statique volatile int pigsEaten = 0 ;
statique final int ENOUGH_PIGS = 5000 ;
public static void main (String[] args) lance InterruptedException {
new PigEater().start();
new PigDigester().start();
}
la classe statique PigEater étend Thread {
@Outrepasser
public void run() {
tandis que (vrai) {
pigs.add(new byte[32 * 1024 * 1024]); //32 Mo par cochon
if (pigsEaten > ENOUGH_PIGS) return ;
prendreANap(100);
}
}
}
la classe statique PigDigester étend Thread {
@Outrepasser
public void run() {
démarrage long = System.currentTimeMillis();
tandis que (vrai) {
prendreANap(2000);
porcsEaten+=pigs.size();
porcs = new ArrayList();
si (porcsMangés > ENOUGH_PIGS) {
System.out.format("%d porcs digérés dans %d ms.%n",pigsEaten, System.currentTimeMillis()-start);
retour;
}
}
}
}
static void takeANap(int ms) {
essayer {
Thread.sleep(ms);
} attraper (Exception e) {
e.printStackTrace();
}
}
}
Nous définissons maintenant le débit de ce système comme « le nombre de porcs pouvant être digérés par seconde ». En considérant qu'un cochon est introduit dans ce python toutes les 100 ms, nous pouvons voir que le débit maximum théorique de ce système peut atteindre 10 cochons/seconde.
Exemple de configuration GC
Jetons un coup d'œil aux performances de l'utilisation de deux systèmes de configuration différents. Quelle que soit la configuration, l'application fonctionne sur un Mac dual-core (OS X10.9.3) avec 8 Go de RAM.
Première configuration :
Tas de 1,4 G (-Xms4g -Xmx4g)
2. Utilisez CMS pour nettoyer l'ancienne génération (-XX:+UseConcMarkSweepGC) et utilisez le collecteur parallèle pour nettoyer la nouvelle génération (-XX:+UseParNewGC)
3. Allouez 12,5% du tas (-Xmn512m) à la nouvelle génération et limitez les tailles de la zone Eden et de la zone Survivor pour qu'elles soient identiques.
La deuxième configuration est légèrement différente :
Tas de 1,2 G (-Xms2g -Xms2g)
2. La nouvelle génération et l'ancienne génération utilisent Parellel GC (-XX:+UseParallelGC)
3. Allouer 75% du tas à la nouvelle génération (-Xmn 1536m)
4. Il est maintenant temps de parier : quelle configuration sera la plus performante (combien de porcs peuvent être mangés par seconde, rappelez-vous) ? Ceux qui ont mis leurs jetons sur la première configuration, vous serez déçus. Le résultat est tout le contraire :
1. La première configuration (grand tas, grande ancienne génération, CMS GC) peut manger 8,2 porcs par seconde
2. La deuxième configuration (petit tas, grande nouvelle génération, Parellel GC) peut manger 9,2 porcs par seconde
Examinons maintenant ce résultat objectivement. Les ressources allouées sont 2 fois moindres mais le débit est augmenté de 12%. Cela va à l’encontre du bon sens et il est donc nécessaire d’analyser plus en profondeur ce qui se passe.
Analyser les résultats du GC
La raison n’est en fait pas compliquée. Vous pouvez trouver la réponse en examinant de plus près ce que fait le GC lors de l’exécution du test. C'est ici que vous choisissez l'outil que vous souhaitez utiliser. Avec l'aide de jstat, j'ai découvert le secret qui se cache derrière cela. La commande ressemble probablement à ceci :
Copiez le code comme suit :
jstat -gc -t -h20 PID 1s
En analysant les données, j'ai remarqué que la configuration 1 a parcouru 1 129 cycles GC (YGCT_FGCT), prenant un total de 63,723 secondes :
Copiez le code comme suit :
Horodatage S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
594,0 174720,0 174720,0 163844,1 0,0 174848,0 131074,1 3670016,0 2621693,5 21248,0 2580,9 1006 63,182 116 0,236 63.419
595,0 174720,0 174720,0 163842,1 0,0 174848,0 65538,0 3670016,0 3047677,9 21248,0 2580,9 1008 63,310 117 0,236 63.546
596,1 174720,0 174720,0 98308,0 163842,1 174848,0 163844,2 3670016,0 491772,9 21248,0 2580,9 1010 63,354 118 0,240 63.595
597,0 174720,0 174720,0 0,0 163840,1 174848,0 131074,1 3670016,0 688380,1 21248,0 2580,9 1011 63,482 118 0,240 63.723
La deuxième configuration a fait une pause au total 168 fois (YGCT+FGCT) et n'a pris que 11,409 secondes.
Copiez le code comme suit :
Horodatage S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
539,3 164352,0 164352,0 0,0 0,0 1211904,0 98306,0 524288,0 164352,2 21504,0 2579,2 27 2,969 141 8,441 11,409
540,3 164352,0 164352,0 0,0 0,0 1211904,0 425986,2 524288,0 164352,2 21504,0 2579,2 27 2,969 141 8,441 11,409
541,4 164352,0 164352,0 0,0 0,0 1211904,0 720900,4 524288,0 164352,2 21504,0 2579,2 27 2,969 141 8,441 11,409
542,3 164352,0 164352,0 0,0 0,0 1211904,0 1015812,6 524288,0 164352,2 21504,0 2579,2 27 2,969 141 8,441 11,409
Considérant que la charge de travail dans les deux cas est égale, par conséquent, dans cette expérience de consommation de porcs, lorsque le GC ne trouve pas d'objets à longue durée de vie, il peut nettoyer les objets poubelles plus rapidement. Avec la première configuration, la fréquence de fonctionnement du GC sera d'environ 6 à 7 fois et le temps de pause total sera de 5 à 6 fois.
Raconter cette histoire répond à deux objectifs. Avant tout, je voulais sortir de mon esprit ce python convulsif. Un autre avantage plus évident est que le réglage GC est une expérience très habile et nécessite que vous ayez une compréhension approfondie des concepts sous-jacents. Bien que celle utilisée dans cet article ne soit qu’une application très courante, les différents résultats de la sélection auront également un impact important sur votre débit et votre planification de capacité. Dans les applications réelles, la différence sera encore plus grande. C'est donc à vous de décider si vous pouvez maîtriser ces concepts, ou vous pouvez simplement vous concentrer sur votre travail quotidien et laisser Plumbr déterminer la configuration GC la plus adaptée à vos besoins.