2010-04-15 4 views
14

c'est mon premier post sur stackoverflow ... J'espère que quelqu'un peut me aiderproblème Java performance avec LinkedBlockingQueue

J'ai une grande régression de performances avec Java 6 LinkedBlockingQueue. Dans le premier fil, je génère des objets que je pousse dans la file d'attente Dans le second fil je tire ces objets. La régression des performances se produit lorsque la méthode take() du LinkedBlockingQueue est appelée fréquemment. J'ai surveillé l'ensemble du programme et la méthode take() a réclamé le plus de temps global. Et le débit passe de ~ 58MB/s à 0,9 Mo/s ...

la pop file d'attente et les méthodes prendre ar appelé avec une méthode statique de cette classe

public class C_myMessageQueue { 

    private static final LinkedBlockingQueue<C_myMessageObject> x_queue = new LinkedBlockingQueue<C_myMessageObject>(50000); 

    /** 
    * @param message 
    * @throws InterruptedException 
    * @throws NullPointerException 
    */ 
    public static void addMyMessage(C_myMessageObject message) 
      throws InterruptedException, NullPointerException { 
     x_queue.put(message); 
    } 

    /** 
    * @return Die erste message der MesseageQueue 
    * @throws InterruptedException 
    */ 
    public static C_myMessageObject getMyMessage() throws InterruptedException { 
     return x_queue.take(); 
    } 
} 

comment puis-je régler la take() méthode pour accomplir au moins 25Mb/s, ou est-il une autre classe que je peux utiliser qui bloquera lorsque la "file d'attente" est pleine ou vide.

Sincères salutations

Bart

P.S .: désolé pour mon mauvais anglais, je suis de l'Allemagne;)

+1

Mo/s est sans signification. take() obtient une référence. Si la charge utile de ce (en dehors du mot de la référence lui-même) est de quelques octets, ou plusieurs mégaoctets, n'est pas pertinent pour la file d'attente. –

+0

je sais ... mais j'ai essayé de forcer l'ampleur de la régression;) – lofthouses

+0

Pouvez-vous d'abord essayer d'ajouter 5000 éléments à la file d'attente puis exécutez les prendre pour voir combien de temps chaque passe dépense? –

Répondre

16

Votre thread producteur simplement met plus d'éléments que le consommateur consomme, de sorte que la file d'attente atteint finalement sa limite de capacité, donc le producteur attend.

La consolidation ma réponse originale puisque nous avons maintenant essentiellement le tableau complet:

  • Vous atteint la limite de débit inhérente à la LinkedBlockingQueue (chaque file d'attente a un) en faisant put() extrêmement rapide de, où même take()s continue, avec aucun traitement ultérieur, ne peut pas suivre. (En passant, cela montre que dans cette structure, sur votre JVM et votre machine de toute façon, les put() sont au moins légèrement plus coûteux que les lectures).
  • Étant donné qu'un verrou particulier est verrouillé par les consommateurs, l'ajout de threads grand public ne pourrait pas vous aider (si votre client effectuait un traitement et limitait le débit, il serait utile d'ajouter plus de consommateurs. un scénario avec plus d'un consommateurs (ou producteurs), vous pouvez essayer SynchronousQueue, ConcurrentLinkedQueue, et le prochain TransferQueue de jsr166y).

Quelques suggestions:

  • Essayez de faire des objets plus gros grains, de sorte que les frais généraux de faire la queue chaque est équilibrée avec le travail réel qui est déchargé du fil produisant (dans votre cas, il semble que vous créez beaucoup de frais généraux de communication pour les objets qui représentent une quantité de travail négligeable)
  • Vous pourriez également demander au producteur d'aider le consommateur en déchargeant un travail de consommation (il ne sert à rien d'attendre inutilement quand il y a du travail à faire).

/jour après John W. a justement souligné ma réponse initiale était trompeuse

+0

Je ne crois pas qu'un StringBuilder.append ("-----"); dans le thread de consommation est tellement plus lent que beaucoup d'opérations arithmétiques dans le thread de producteur – lofthouses

+0

S'il vous plaît préciser la taille de la file d'attente lorsque le problème se produit. C'est la seule façon de savoir si c'est le producteur ou le consommateur qui est lent. Les opérations de chaînes peuvent également être lentes, ne dépendent pas de telles suppositions, juste au moment où vous remarquez le problème, vérifiez simplement si la file d'attente est presque vide ou presque pleine. –

+0

(Je suppose que vous créez des objets très fins inutilement Vous pouvez également essayer de regrouper certains de ces objets dans des packs plus gros afin de réduire la charge de la file d'attente, si nécessaire). –

1

Il est difficile de dire ce qui se passe sans savoir quelque chose au sujet du processus de remplissage.

Si addMyMessage est appelée moins fréquemment - peut-être à cause d'un problème de performance dans une partie de votre application - la méthode take doit attendre. De cette façon, il semble que take est le coupable, mais en fait c'est la partie de remplissage de votre application.

+0

Le thread de remplissage n'est pas le coupable, le thread de remplissage attend sur le fil du consommateur lorsque le débit diminue (vu avec le profileur visualvm). quand le fil de remplissage n'a pas attendu le consommateur, le débit est vraiment bon, au moins 58Mb/s. le thread de consommation attend sur la méthode de prise, les opérations dans la tâche du consommateur sont minimes ... J'ai essayé aussi le ArrayBlockingQueue ... le même jeu, le consommateur attend la méthode slow take, et le remplissage attend parce que la queue est plein ps: le programme est une tâche de copie pps: merci pour votre réponse;) – lofthouses

0

Il est possible que votre application soit affectée par les modifications liées au verrouillage de Java 6, en particulier la fonction de "verrouillage biaisé". Essayez de le désactiver en utilisant le commutateur -XX:-UseBiasedLocking et voyez si cela fait une différence.

Voir ce pour plus d'informations: http://java.sun.com/performance/reference/whitepapers/6_performance.html

+0

Merci pour l'indice, mais il n'a rien changé ... take() prend 96,7% du temps dans le thread de consommation. à mon avis la méthode de prise est de ralentir ... – lofthouses

+0

Peut-être que cela vous aide si vous regardez la mise en œuvre de cette méthode. Je ne pouvais pas trouver quelque chose de bizarre là-dedans. –

+0

je ne pouvais rien trouver, mais le fait est que prendre a pris la plupart du temps dans ce fil – lofthouses

1

Trouvé this interesting post sur les problèmes de performance en raison de la taille et la file d'attente collecte des ordures.

+0

Hmm, un article intersting, mais il n'a rien changé pour mon problème ... – lofthouses

0

Ne peux rien dire à coup sûr. Mais vous pouvez essayer de changer l'implémentation BlockingQueue (juste comme une expérience).

Vous définissez la capacité initiale 50k et utilisez LinkedBlockingQueue. Essayez ArrayBlockingQueue avec la même capacité, vous pouvez également jouer avec le paramètre fair.

+0

ce sera mon dernier essai ... et puis Je vais jouer avec javolution ou quelque chose comme ça ... ou est-ce une mauvaise idée? – lofthouses

+0

Je pense qu'il veut dire un scénario de producteur unique, consommateur unique, donc l'équité ne devrait pas être pertinente. Curieux que LinkedBlockingQueue n'expose pas un paramètre juste si. –

3

Je recommande généralement de ne pas utiliser LinkedBlockingQueue dans une zone de code sensible aux performances, utilisez une ArrayBlockingQueue. Il donnera un profil de récupération de place beaucoup plus agréable et est plus favorable au cache que le LinkedBlockingQueue.

Essayez l'ArrayBlockingQueue et mesurez les performances. Le seul avantage de LinkedBlockingQueue est qu'il peut être illimité, mais c'est rarement ce que vous voulez réellement.Si vous avez un cas où un consommateur échoue et que les files d'attente commencent à effectuer une sauvegarde, le fait d'avoir des files d'attente bornées permet au système de se dégrader correctement plutôt que de risquer OutOfMemoryErrors qui peut se produire si les files d'attente sont illimitées.

+0

Vous avez raison, mais il n'y a qu'une différence de performance minimale entre arrayBQ et linkedBQ. et la régression résiste encore – lofthouses

3

Voici quelques choses à essayer:

Remplacer le LinkedBlockingQueue avec un ArrayBlockingQueue. Il n'a pas de références qui pendent et il vaut mieux se comporter lorsque la file d'attente se remplit. Plus précisément, compte tenu de l'implémentation de LinkedBlockingQueue 1.6, le GC complet des éléments n'aura pas lieu tant que la file d'attente ne sera pas vide. Si le côté du producteur est constamment en train d'effectuer le côté consommateur, pensez à utiliser drain ou drainTo pour effectuer une opération de prise en masse.

Vous pouvez également demander à la file d'attente de prendre des tableaux ou des listes d'objets de message. Le producteur remplit une liste ou un tableau avec des objets de message et chaque put ou take déplace plusieurs messages avec le même temps de verrouillage. Pensez-y comme une secrétaire vous remettant une pile de messages «Pendant que vous étiez sorti» plutôt que de vous les remettre un à la fois.

+0

damn, pourquoi je ne l'ai pas essayé moi-même ... donc je vais l'essayer maintenant :) – lofthouses

+0

Je doute que le drainTo() aiderait. Le problème du consommateur n'est pas la contention du verrou du consommateur (c'est un seul thread consommateur, prendre le verrou du consommateur n'est pas accepté et/fast /). Le problème fondamental est que le consommateur est lent, qu'il y a moins de flux de données, donc le producteur, peu importe (sans une file d'attente débordante), va être limité par ce flux moindre - voir aussi ma réponse. –

+0

ArrayBlockingQueue sera plus lent que LinkedBlockingQueue car il utilise single lock pour put() et take() alors que LinkedBlockingQueue utilise 2 verrous pour put() et take() et ne synchronise producteur et consommateur que lorsque la file est vide/pleine. –

0

Si les frais généraux de performance brute pour mettre et prendre objet de vos blockingqueues est votre goulot d'étranglement (et non le slow-producer/consumer problem), vous pouvez obtenir Amélioration considérable des performances avec le traitement par lot d'objets: par exemple, au lieu de mettre ou de prendre des objets à grain fin, vous mettez ou prenez des listes d'objets à granularité grossière.Voici un extrait de code:

ArrayBlockingQueue<List<Object>> Q = new ArrayBlockingQueue<List<Object>>(); 

// producer side 
List<Object> l = new ArrayList<Object>(); 
for (int i=0; i<100; i++) { 
    l.add(i); // your initialization here 
} 
Q.put(l); 

// consumer side 
List<Object> l2 = Q.take(); 
// do something 

Le traitement par lots peut augmenter vos performances d'un ordre de grandeur.