2010-12-15 78 views
8

J'ai récemment commencé le développement d'une application faisant un usage intensif du réseautage. Une première tentative a été faite en utilisant RMI et pour plusieurs raisons, nous sommes passés à des sockets purs. Cependant, lors du test de sockets sur un réseau, ou même sur localhost, nous avons baissé à un taux de 25 demandes/seconde. Tout en utilisant RMI, il était supérieur de deux ordres de grandeur.Goulot d'étranglement des performances du socket Java: où?

Avec un peu plus de tests, nous avons obtenu à la suite (pour localhost):

  • Envoi toujours le même objet: 31628 requêtes/s
  • Envoi toujours un nouvel objet: 25 demandes/sec
  • Seul le taux de création d'objet: 3-4 millions par seconde (si ce n'est pas le goulot d'étranglement)

Voici le code client: (côté serveur répond juste un « accusé de réception »)

public static void main(String[] args) throws IOException, ClassNotFoundException 
{ 
    Socket kkSocket = null; 
    ObjectOutputStream out = null; 
    ObjectInputStream in = null; 


    kkSocket = new Socket("barium", 4444); 
    out = new ObjectOutputStream(kkSocket.getOutputStream()); 
    in = new ObjectInputStream(kkSocket.getInputStream()); 


    long throughput; 
    long millis; 

    TcpRequest hello = null; 


    throughput = 0; 
    millis = System.currentTimeMillis(); 
    while (System.currentTimeMillis() < millis + 1000) 
    { 
     hello = new TcpRequest(); 
     hello.service = "hello"; 
     hello.payload = Math.random(); 
     throughput++; 
    } 

    System.out.println("-------------------------------------------------------"); 
    System.out.println("|  Objects created: " + (throughput) + " requests/sec."); 
    System.out.println("-------------------------------------------------------"); 


    throughput = 0; 
    millis = System.currentTimeMillis(); 
    while (System.currentTimeMillis() < millis + 1000) 
    { 
     out.writeObject(hello); 
     Object res = in.readObject(); 
     throughput++; 
    } 
    System.out.println("-------------------------------------------------------"); 
    System.out.println("|  Same object throughput: " + (throughput) + " requests/sec."); 
    System.out.println("-------------------------------------------------------"); 


    throughput = 0; 
    millis = System.currentTimeMillis(); 
    while (System.currentTimeMillis() < millis + 1000) 
    { 
     hello = new TcpRequest(); 
     out.writeObject(hello); 
     Object res = in.readObject(); 
     throughput++; 
    } 
    System.out.println("-------------------------------------------------------"); 
    System.out.println("|  New objetcs throughput: " + (throughput) + " requests/sec."); 
    System.out.println("-------------------------------------------------------"); 


    out.close(); 
    in.close(); 

    kkSocket.close(); 
} 

La classe TcpRequest est juste une classe factice sans rien de spécial. Donc, si créer un objet est rapide, si l'envoyer sur le réseau est rapide ... pourquoi envoyer un nouvel objet sur le réseau si lentement?!?!

Et si vous gardez un même objet et de modifier son contenu avant de l'envoyer, vous aurez également un taux élevé de transfert ... mais tomber dans le piège méchant:

Lorsque vous travaillez avec sérialisation il est important de garder à l'esprit que le ObjectOutputStream maintient une hashtable mappage des objets écrits dans le flux vers un descripteur. Lorsqu'un objet est écrit dans le flux pour la première fois, son contenu sera copié dans le flux. Toutefois, les écritures ultérieures résultent en un handle vers l'objet en cours d'écriture dans le flux .

... ce qui nous est arrivé et a causé quelques heures de débogage avant de le découvrir.

Donc, fondamentalement ... comment atteindre un débit élevé avec les sockets? (... Je veux dire, avec RMI étant une enveloppe autour de nous nous étions déjà deux ordres de grandeur plus haut!)

résolu:

En remplaçant:

out = new ObjectOutputStream(kkSocket.getOutputStream()); 

Avec:

out = new ObjectOutputStream(new BufferedOutputStream(kkSocket.getOutputStream())) 

Les performances sont à nouveau normale (à peu près le même débit élevé avec le même cas de l'objet)

+1

Identique à ce numéro: http://stackoverflow.com/questions/2251051/ performance-issue-using-javas-objet-streams-with-sockets ... pas de réponses non plus. – dagnelies

+0

Si vos objets ne se réfèrent pas mutuellement, vous pouvez appeler ObjectOutputStream.reset() après l'écriture pour empêcher le flux de se souvenir de tous les objets qu'il a écrits. Bien qu'il provoque généralement des problèmes de mémoire sur le côté de lecture, pas de performances sur le côté serveur. Avez-vous essayé de profiler votre application pour voir où elle passe le plus de temps? –

Répondre

6

Trouvés:

Au lieu de:

out = new ObjectOutputStream(kkSocket.getOutputStream()); 

Vous devez utiliser:

out = new ObjectOutputStream(new BufferedOutputStream(kkSocket.getOutputStream())); 

Et

out.flush(); 

lors de l'envoi d'un message.

... fait une énorme différence ... mais je ne sais pas exactement pourquoi.

+0

Cela peut faire partie du problème, mais seulement une partie de celui-ci. S'il vous plaît voir mon commentaire. –

+1

Je peux expliquer cela. Si vous utilisez un flux qui n'utilise pas de buffer, chaque 'write' aboutit à un syscall. Les frais généraux peuvent être des milliers d'instructions machine supplémentaires par syscall. –

+0

ObjectOutputStream exécute son propre tampon de 1k en interne. Ajouter un BufferedOutputStream dans la pile ne peut pas vraiment faire la différence. – EJP

1

Je ne ferais pas confiance aux résultats de ce cas-test. Cela ne garantit pas que la JVM est réchauffée; c'est-à-dire que les classes sont chargées, initialisées et compilées en code natif avant de mesurer les temps d'exécution. Il y a de fortes chances que la compilation du code natif donne un coup de pouce dans l'exécution du test de performance, ce qui gonfle le temps pris par une ou plusieurs des boucles.

0

Le problème que vous rencontrez n'est pas le faible débit avec les sockets; c'est la sérialisation Java par défaut lente.

Lorsque vous envoyez le même objet encore et encore, il n'est réellement sérialisé qu'une seule fois, puis une référence est envoyée à chaque fois; cela explique pourquoi ça va si vite. Lorsque vous créez un nouvel objet à chaque fois, ce nouvel objet doit être sérialisé à l'aide du mécanisme de sérialisation Java relativement lent, qui est probablement beaucoup plus complexe que ce dont vous avez besoin. Pour améliorer cela, vous pouvez implémenter un code de sérialisation personnalisé pour votre classe ou créer votre propre protocole de sérialisation d'objet externe à la classe (et utiliser DataOutput au lieu de ObjectOutputStream).

Cet article a beaucoup de bonnes informations, même si elle est un peu daté: http://java.sun.com/developer/technicalArticles/Programming/serialization/ Voir la dernière partie sur les considérations de performance

+0

Le problème n'est pas la «sérialisation Java par défaut lente», c'est la différence entre sérialiser un objet et un handle. Le test qui ne fait que renvoyer le même objet n'exerce en réalité rien d'intéressant, de sorte que ses résultats ne présentent aucun intérêt non plus. – EJP