11
votes

Le collecteur de déchets en série de Java se produisant beaucoup mieux que les autres collectionneurs à déchets?

Je teste une API, écrite en Java, qui devrait minimiser la latence dans le traitement des messages reçus sur un réseau. Pour atteindre ces objectifs, je joue avec les différents collectionneurs à ordures disponibles.

J'essaie quatre techniques différentes, qui utilisent les indicateurs suivants pour contrôler la collecte des ordures:

1) série: -xx: + usingerialgc

2) Parallèlement: -XX: + USEPARELLOOLDGC

3) Concurrent: -XX: + USECONCMARKSWEEPGC

4) Concurrent / incrémental: -XX: + USECONCMARKSWEEPGC -XX: + CMSInCremenceMlode -XX: + CMSInCrementialPaçage

J'ai couru chaque technique au cours de cinq heures. J'ai utilisé périodiquement la liste de Garbagecolectormxbean fournie par GestionFactory.getgarbagecolectormxbeans () pour récupérer le temps total passé à la collecte des ordures.

Mes résultats? Notez que "la latence" Voici "la quantité de temps que mon application + l'API a passé à traiter chaque message cueilli du réseau."

série: 789 gc Événements totalisant 1309 ms; Latence moyenne 47,45 US, Latence médiane 8.704 US, Max Latence 1197 US

Parallèle: 1715 GC Événements totalisant 122518 MS; Latence moyenne 450.8 US, Latence médiane 8.448 US, Max Latence 8292 US

Concurrent: 4629 GC Événements totalisant 116229 MS; Latence moyenne 707.2 US, Latence médiane 9.216 US, Max Latence 9151 US

incrémental: 5066 gc événements totalisant 200213 ms; Latence moyenne 515.9 US, Latence médiane 9.472 US, Max Latence 14209 US

Je trouve que ces résultats seront si improbables qu'ils fronquent des absurdes. Est-ce que quelqu'un sait pourquoi je pourrais avoir ce genre de résultats?

OH, et pour l'enregistrement, j'utilise Java Hotspot (TM) Serveur 64 bits VM.


4 commentaires

Vous supposez que l'exécution de deux choses en parallèle est nécessairement plus rapide que d'exécuter une chose après l'autre?


Je m'attendrais à ce que la latence maximale montent bien que


Ainsi, combien de messages ont été traités dans ces 5 heures dans vos différents scénarios? Exécutez-vous un seul fil ou multithread?


J'ai traité 431,8 millions de messages à chaque fois. L'application utilise deux threads - une sur le chemin critique saisit les messages du fil et les emballes dans une file d'attente. Celui sur le chemin non critique les quitte hors de la file d'attente et les place dans une file d'attente prioritaire; Ensuite, une fois une seconde, il draine la file d'attente prioritaire et calcule les statistiques médian / moyenne / max / etc. de la latence pour cette seconde. Cette machine dispose de deux 6 cœurs Intel Xeon 5680s et de 24 Go de RAM.


5 Réponses :


18
votes

Je travaille sur une application Java qui devrait optimiser le débit et minimiser la latence p>

Deux problèmes avec ceux-ci: p>

  • Ce sont souvent des objectifs contradictoires, vous devez donc décider comment em> important chacun est contre l'autre (sacrifiez-vous 10% de latence pour obtenir un gain de 20% de débit ou vice versa? Voulez-vous que certains Cible de latence spécifique em>, au-delà de laquelle il n'a pas d'importance, que ce soit plus rapide? Des choses comme ça.) Li>
  • Vous n'avez donné aucun résultat autour de ni em> de ces li> ul>

    Tout ce que vous avez montré est de combien de temps est passé dans le collecteur des ordures. Si vous réellement em> réalise plus de débit, vous seriez probablement attendre em> pour voir plus de temps passé dans le collecteur des ordures. Ou de la mettre d'autre moyen, je peux modifier le code pour minimiser les valeurs que vous rapportez vraiment facilement: P>

    // Avoid generating any garbage
    Thread.sleep(10000000);
    


4 commentaires

+1 grande réponse. J'aimerais pouvoir donner un supplément +1 pour votre solution pour éviter de générer des ordures :-)


Trois choses. Premièrement, je comprends que les objectifs sont souvent contradictoires. Je suppose que "la latence" serait mon objectif principal. Deuxièmement, je ne suis pas seulement itérant à travers un fichier ou quelque chose comme ça. Les applications traitent le trafic réseau (même ensemble de trafic pour chaque exécution de l'application), de sorte que la quantité de données traitée est la même sur chaque course. Troisièmement, je posterai mes résultats de la latence dans mon message principal dans un instant.


L'utilisateur a posé une question juste avec des données spécifiques, faisant de son mieux. Désolé Jon, pour moi est un bowvote pour vous, cette réponse est trop générique et ne donne vraiment aucune perspicacité ni direction à l'utilisateur et à tous les lecteurs.


@ Massimo: Il convient de noter que les données de latence ont été ajoutées après avoir répondu à la question. Il n'incluait à l'origine que le timing GC. Je ne vous demande pas de supprimer votre bowvote, ni je vais prendre des douleurs pour éditer une réponse de 6 ans cependant.



0
votes

Vous ne pouvez pas dire qu'un gC est meilleur que l'autre. Cela dépend de vos besoins et de votre application.

Mais si vous voulez maximiser le débit et minimiser la latence: GC est votre ennemi! Vous ne devez pas appeler GC du tout et essayer d'empêcher JVM d'appeler GC.

Allez avec des pools d'objets série et utilisez des objets.


0 commentaires

4
votes

Je ne trouve pas ce surprenant du tout.

Le problème avec la collecte des ordures en série est que, alors qu'il est en cours d'exécution, rien d'autre ne peut courir du tout (AKA "arrête le monde"). Cela a un bon point: il conserve la quantité de travail consacrée à la collecte des ordures à peu près son minimum.

Presque toute sorte de collection de déchets parallèle ou simultanée doit faire une juste quantité de travail supplémentaire pour que toutes les modifications apportées au tas apparaissent atomiques au reste du code. Au lieu de tout arranger tout pour un moment, il doit cesser juste ces choses qui dépendent d'un changement particulier, puis aussi longtemps pour effectuer ce changement spécifique. Il laisse ensuite ce code commencer à courir à nouveau, passe au point suivant qu'il va faire une modification, arrête d'autres pièces de code qui en dépendent, etc.

L'autre point (cependant, dans ce cas, probablement assez mineur), c'est que lorsque vous traitez plus de données, vous vous attendez généralement à générer plus de déchets et à passer plus de temps à faire de la collection à la poubelle. Étant donné que le collecteur de série arrête tout autre traitement alors qu'il fait son travail, cela ne fait pas seulement seulement la collection de déchets rapidement, mais empêche également plus de déchets d'être générés au cours de cette période.

Maintenant, pourquoi dois-je dire que c'est probablement un contributeur mineur dans ce cas? C'est assez simple: le collecteur de série seulement a utilisé un peu plus d'une seconde sur cinq heures. Même si rien d'autre n'a été fait pendant cela ~ 1,3 seconde, c'est un tel pourcentage de cinq heures de cinq heures qu'il ne faisait probablement pas beaucoup de différence (le cas échéant) de votre débit global.

Résumé: Le problème avec la collecte des ordures en série n'est-ce pas qu'il utilise un temps excessif dans l'ensemble - c'est qu'il peut être très gênant s'il empêche le monde de la droite lorsque vous avez besoin de réponse rapide. Dans le même temps, je devrais ajouter cela tant que vos cycles de collecte sont courts, cela peut toujours être assez minimal. En théorie, les autres formes de GC limitent principalement votre pire des cas, mais en fait (par exemple, en limitant la taille du tas), vous pouvez souvent limiter votre latence maximale avec un collecteur série également.


0 commentaires

2
votes

Il y avait une excellente conversation par un ingénieur Twitter à l'année 2012 conférence QON sur ce sujet - vous pouvez le regarder < Un href = "http://www.infoq.com/presentions/jvm-performance-tuning-twitter-qcon-london-2012" rel = "nofollow"> ici .

Il a discuté des différentes "générations" dans la collection Hotspot JVM Memory and Gorbage (Eden, Survivor, Old). Notez notamment que le "concurrent" dans ConcurrentMarkandswep s'applique uniquement à la vieille génération, c'est-à-dire des objets qui traînent pendant un moment.

Les objets de courte durée sont de la GCD de la génération "Eden" - ceci est bon marché, mais est un événement GC "stop-the-world" quel que soit l'algorithme de GC que vous avez choisi!

Le conseil était d'accorder la jeune génération d'abord par ex. Allouer beaucoup de nouveaux Eden, il y a donc plus de chances que les objets meurent jeunes et être récupérés à moindre coût. Utilisez + PrintGCDétails, + PrintHeapATGC, + PrintTenuringDistribution ... Si vous obtenez plus de 100% de survivant, il n'y avait pas de place, alors les objets sont rapidement promus au vieil - c'est mauvais.

Lors de la syntonisation de l'ancien Generatiohn, si la latence est la priorité absolue, il a été recommandé d'essayer Parallallold avec Auto-Tune (+ AdaptiveSizePolicy, etc.), puis essayez CMS, puis peut-être le nouveau G1GC.


2 commentaires

Les diapositives sont également disponibles à Slideshare. NET / ASZEGI / ... , si le lien ci-dessus ne fonctionne pas pour vous.


Merci - J'ai également mis à jour le lien dans ma réponse pour pointer vers le nouvel emplacement de la vidéo.



0
votes

avec la collection série, une seule chose se passe à la fois. Par exemple, même lorsque plusieurs processeurs sont disponible, un seul est utilisé pour effectuer la collection. Lorsque la collecte parallèle est utilisée, la tâche de La collecte des ordures est divisée en parties et ces sous-parties sont exécutées simultanément, sur différents Cpus. L'opération simultanée permet à la collection d'être effectuée plus rapidement, au détriment de une complexité supplémentaire et une fragmentation potentielle.

Bien que le gc série utilise un seul thread pour traiter un GC, le GC parallèle utilise plusieurs threads pour traiter un GC et donc plus vite. Ce GC est utile quand il y a suffisamment de mémoire et un grand nombre de cœurs. Il s'appelle également le "GC du débit".


0 commentaires