99 votes

Méthode la plus rapide (faible latence) pour la communication interprocessus entre Java et C/C++.

J'ai une application Java qui se connecte via un socket TCP à un "serveur" développé en C/C++.

L'application et le serveur fonctionnent sur la même machine, une boîte Solaris (mais nous envisageons de migrer vers Linux à terme). Le type de données échangées est constitué de messages simples (login, login ACK, puis le client demande quelque chose, le serveur répond). Chaque message fait environ 300 octets.

Actuellement, nous utilisons des sockets, et tout va bien, mais je cherche un moyen plus rapide d'échanger des données (latence plus faible), en utilisant des méthodes IPC.

J'ai fait des recherches sur le net et j'ai trouvé des références aux technologies suivantes :

  • Mémoire partagée
  • tuyaux
  • files d'attente
  • ainsi que ce que l'on appelle le DMA (Direct Memory Access).

mais je n'ai pas trouvé d'analyse correcte de leurs performances respectives, ni comment les implémenter à la fois en JAVA et en C/C++ (pour qu'ils puissent se parler), à l'exception peut-être de tuyaux que j'ai pu imaginer comment faire.

Quelqu'un peut-il commenter les performances et la faisabilité de chaque méthode dans ce contexte ? Un lien vers des informations utiles sur la mise en œuvre ?


EDIT / UPDATE

En suivant les commentaires et les réponses que j'ai reçus ici, j'ai trouvé des informations sur les Unix Domain Sockets, qui semblent être construites juste au-dessus des pipes, et qui m'épargneraient toute la pile TCP. C'est spécifique à la plateforme, donc je prévois de le tester avec JNI ou soit juds ou junixsocket .

Les prochaines étapes possibles seraient l'implémentation directe des pipes, puis la mémoire partagée, bien qu'on m'ait prévenu du niveau de complexité supplémentaire...


merci pour votre aide

102voto

Andriy Points 606

Je viens de tester la latence de Java sur mon Corei5 2.8GHz, avec un seul octet envoyé/reçu, 2 processus Java viennent d'être créés, sans assigner de cores CPU spécifiques avec le jeu de tâches :

TCP         - 25 microseconds
Named pipes - 15 microseconds

Maintenant, spécifier explicitement les masques de noyau, comme jeu de tâches 1 java Srv ou jeu de tâches 2 java Cli :

TCP, same cores:                      30 microseconds
TCP, explicit different cores:        22 microseconds
Named pipes, same core:               4-5 microseconds !!!!
Named pipes, taskset different cores: 7-8 microseconds !!!!

donc

TCP overhead is visible
scheduling overhead (or core caches?) is also the culprit

Dans le même temps, Thread.sleep(0) (qui, comme le montre strace, provoque l'exécution d'un seul appel au noyau Linux sched_yield()) prend 0,3 microseconde - donc les tubes nommés programmés sur un seul cœur ont encore beaucoup de surcharge.

Une mesure de la mémoire partagée : 14 septembre 2009 - Solace Systems a annoncé aujourd'hui que son API de plate-forme de messagerie unifiée peut atteindre une latence moyenne de moins de 700 nanosecondes en utilisant un transport à mémoire partagée. http://solacesystems.com/news/fastest-ipc-messaging/

P.S. - j'ai essayé la mémoire partagée le lendemain sous la forme de fichiers mappés en mémoire, si l'attente occupée est acceptable, on peut réduire la latence à 0,3 microseconde pour passer un seul octet avec un code comme celui-ci :

MappedByteBuffer mem =
  new RandomAccessFile("/tmp/mapped.txt", "rw").getChannel()
  .map(FileChannel.MapMode.READ_WRITE, 0, 1);

while(true){
  while(mem.get(0)!=5) Thread.sleep(0); // waiting for client request
  mem.put(0, (byte)10); // sending the reply
}

Notes : Thread.sleep(0) est nécessaire pour que 2 processus puissent voir les changements de l'autre. (je ne connais pas encore d'autre moyen). Si 2 processus sont forcés au même noyau avec taskset, la latence devient 1.5 microsecondes - c'est un délai de changement de contexte

P.P.S - et 0,3 microseconde est un bon chiffre ! Le code suivant prend exactement 0,1 microseconde, tout en effectuant une concaténation primitive de chaînes de caractères uniquement :

int j=123456789;
String ret = "my-record-key-" + j  + "-in-db";

P.P.P.S - j'espère que ce n'est pas trop hors sujet, mais j'ai finalement essayé de remplacer Thread.sleep(0) par l'incrémentation d'une variable int statique volatile (il se trouve que la JVM vide les caches du CPU en le faisant) et j'ai obtenu - record ! - latence de 72 nanosecondes communication entre processus java et java !

Cependant, lorsqu'elles sont forcées de travailler sur le même cœur de processeur, les JVM à incrémentation volatile ne se cèdent jamais le contrôle, produisant ainsi une latence d'exactement 10 millisecondes - le quantum de temps de Linux semble être de 5 ms... Donc, ceci ne devrait être utilisé que s'il y a un coeur libre - sinon sleep(0) est plus sûr.

10voto

MSalters Points 74024

Le DMA est une méthode par laquelle les périphériques matériels peuvent accéder à la RAM physique sans interrompre le CPU. Un exemple courant est un contrôleur de disque dur qui peut copier des octets directement du disque vers la RAM. En tant que tel, il n'est pas applicable à l'IPC.

La mémoire partagée et les tuyaux sont tous deux directement pris en charge par les systèmes d'exploitation modernes. En tant que tels, ils sont assez rapides. Les files d'attente sont généralement des abstractions, c'est-à-dire qu'elles sont implémentées au-dessus des sockets, des pipes et/ou de la mémoire partagée. Cela peut sembler être un mécanisme plus lent, mais l'alternative est la suivante vous créer une telle abstraction.

10voto

Peter Lawrey Points 229686

La question a été posée il y a un certain temps, mais vous pourriez être intéressé par les éléments suivants https://github.com/peter-lawrey/Java-Chronicle qui supporte des latences typiques de 200 ns et des débits de 20 M de messages/seconde. Il utilise des fichiers mappés en mémoire partagés entre les processus (il assure également la persistance des données, ce qui en fait le moyen le plus rapide de conserver les données).

7voto

sustrik Points 69

Voici un projet contenant des tests de performance pour différents transports IPC :

http://github.com/rigtorp/ipc-bench

6voto

bakkal Points 13449

Si vous envisagez un jour d'utiliser l'accès natif (puisque votre application et le "serveur" se trouvent sur la même machine), pensez à JNA il y a moins de code passe-partout à gérer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X