méthode la plus rapide (faible latence) pour la communication inter-processus entre Java et C / C ++


100

J'ai une application Java, connectée via une 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 de simples messages (login, login ACK, puis le client demande quelque chose, le serveur répond). chaque message mesure environ 300 octets.

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

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

  • la memoire partagée
  • tuyaux
  • files d'attente
  • ainsi que ce qu'on appelle DMA (Direct Memory Access)

mais je n'ai pas pu trouver une analyse correcte de leurs performances respectives, ni comment les implémenter en JAVA et C / C ++ (pour qu'ils puissent se parler), sauf peut-être des tuyaux que je pourrais imaginer comment faire.

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


MODIFIER / METTRE À JOUR

suite au commentaire et aux réponses que j'ai obtenus ici, j'ai trouvé des informations sur les sockets de domaine Unix, qui semblent être construites juste au-dessus des tuyaux, et me sauveraient toute la pile TCP. c'est spécifique à la plate-forme, donc je prévois de le tester avec JNI ou juds ou junixsocket .

Les prochaines étapes possibles seraient l'implémentation directe des tuyaux, puis la mémoire partagée, même si j'ai été averti du niveau supplémentaire de complexité ...


Merci de votre aide


7
C'est peut-être exagéré dans votre cas, mais considérez zeromq.org
jfs

c'est intéressant, mais l'idée serait d'utiliser d'abord les méthodes «génériques» (comme dans le système d'exploitation ou dans le langage), c'est pourquoi j'ai mentionné les files d'attente et la mémoire partagée.
Bastien


N'oubliez pas les fichiers mappés ou simplement UDP.

10
UDP plus lent que TCP ??? hmmm ... preuve s'il vous plaît
Boppity Bop

Réponses:


103

Je viens de tester la latence de Java sur mon Corei5 2,8 GHz, un seul octet envoyé / reçu, 2 processus Java juste générés, sans attribuer des cœurs de processeur spécifiques avec le jeu de tâches:

TCP         - 25 microseconds
Named pipes - 15 microseconds

Spécifiant maintenant explicitement les masques de base, comme le jeu de tâches 1 java Srv ou le 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 !!!!

alors

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, entraîne l'exécution d'un seul appel de noyau Linux sched_yield ()) prend 0,3 microseconde - les tubes nommés planifiés sur un seul cœur ont donc encore beaucoup de surcharge

Quelques mesures de 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 de mémoire partagée. http://solacesystems.com/news/fastest-ipc-messaging/

PS - a essayé la mémoire partagée le lendemain sous la forme de fichiers mappés en mémoire, si une attente occupée est acceptable, nous pouvons 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 forcés au même cœur avec un ensemble de tâches, la latence devient 1,5 microsecondes - c'est un délai de changement de contexte

PPS - et 0,3 microseconde est un bon nombre! Le code suivant prend exactement 0,1 microseconde, tout en effectuant une concaténation de chaîne primitive uniquement:

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

PPPS - j'espère que ce n'est pas trop hors sujet, mais finalement j'ai essayé de remplacer Thread.sleep (0) par l'incrémentation d'une variable int statique volatile (JVM arrive à vider les caches CPU en le faisant) et obtenu - enregistrez! - Communication de processus java à java de latence de 72 nanosecondes !

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


merci Andriy, étude très informative, et elle correspond plus ou moins à mes mesures pour TCP, donc c'est une bonne référence. Je suppose que je vais examiner les tuyaux nommés.
Bastien

Donc, remplacer le Thread (Sleep) par l'incrémentation de l'int statique volatile ne devrait être fait que si vous pouvez épingler un processus à différents cœurs? Aussi, je ne savais pas que vous pouviez faire ça? Je pensais que le système d'exploitation décidait?
mezamorphique

3
Essayez LockSupport.parkNanos (1), devrait faire la même chose.
reccles le

Très agréable. Vous pouvez cependant faire mieux (comme dans la latence RTT 5-7us) pour le ping TCP. Voir ici: psy-lob-saw.blogspot.com/2012/12/…
Nitsan Wakart

1
Exploration plus approfondie de l'utilisation d'un fichier mappé en mémoire comme mémoire partagée pour prendre en charge la file d'attente IPC en Java: psy-lob-saw.blogspot.com/2013/04/lock-free-ipc-queue.html, atteignant 135 millions de messages par seconde. Voir également ma réponse ci-dessous pour une étude comparative de la latence par méthode.
Nitsan Wakart

10

DMA est une méthode par laquelle les périphériques matériels peuvent accéder à la RAM physique sans interrompre le CPU. Par exemple, 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 ne s'applique pas à IPC.

La mémoire partagée et les canaux sont tous deux pris en charge directement 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, par exemple implémentées sur des sockets, des tubes et / ou de la mémoire partagée. Cela peut ressembler à un mécanisme plus lent, mais l'alternative est que vous créez une telle abstraction.


pour DMA, pourquoi alors je peux lire beaucoup de choses liées à RDMA (comme Remote Direct Memory Access) qui s'appliqueraient à travers le réseau (en particulier avec InfiniBand) et faire la même chose. J'essaie en fait d'atteindre l'équivalent SANS le réseau (car tout est sur la même boîte).
Bastien

RDMA est le même concept: copier des octets sur un réseau sans interrompre les processeurs de chaque côté. Il ne fonctionne toujours pas au niveau du processus.
MSalters

10

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



6

Si jamais vous envisagez d'utiliser l'accès natif (puisque votre application et le "serveur" sont sur la même machine), pensez à JNA , il a moins de code standard à gérer.


6

Une arrivée tardive, mais souhaitait signaler un projet open source dédié à la mesure de la latence ping à l'aide de Java NIO.

Plus exploré / expliqué dans ce billet de blog . Les résultats sont (RTT en nanos):

Implementation, Min,   50%,   90%,   99%,   99.9%, 99.99%,Max
IPC busy-spin,  89,    127,   168,   3326,  6501,  11555, 25131
UDP busy-spin,  4597,  5224,  5391,  5958,  8466,  10918, 18396
TCP busy-spin,  6244,  6784,  7475,  8697,  11070, 16791, 27265
TCP select-now, 8858,  9617,  9845,  12173, 13845, 19417, 26171
TCP block,      10696, 13103, 13299, 14428, 15629, 20373, 32149
TCP select,     13425, 15426, 15743, 18035, 20719, 24793, 37877

Cela va dans le sens de la réponse acceptée. L'erreur System.nanotime () (estimée en ne mesurant rien) est mesurée à environ 40 nanos, donc pour l'IPC, le résultat réel peut être inférieur. Prendre plaisir.


2

Je ne connais pas grand-chose à la communication inter-processus native, mais je suppose que vous devez communiquer en utilisant du code natif, auquel vous pouvez accéder à l'aide de mécanismes JNI. Ainsi, à partir de Java, vous appelleriez une fonction native qui communique avec l'autre processus.



0

Avez-vous envisagé de garder les prises ouvertes pour que les connexions puissent être réutilisées?


les prises restent ouvertes. la connexion est active pendant toute la durée de l'exécution de l'application (environ 7 heures). les messages sont échangés plus ou moins en continu (disons environ 5 à 10 par seconde). la latence actuelle est d'environ 200 microsecondes, l'objectif est de raser 1 ou 2 ordres de grandeur.
Bastien

Une latence de 2 ms? Ambitieux. Serait-il possible de réécrire le C-stuff dans une bibliothèque partagée que vous pouvez interfacer à l'aide de JNI?
Thorbjørn Ravn Andersen

2 ms équivaut à 2000 microsecondes, pas 200. cela rend 2 ms beaucoup moins ambitieux.
thewhiteambit

-1

Rapport de bogue Oracle sur les performances JNI: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4096069

JNI est une interface lente et les sockets Java TCP sont donc la méthode la plus rapide pour la notification entre les applications, mais cela ne signifie pas que vous devez envoyer la charge utile via une socket. Utilisez LDMA pour transférer la charge utile, mais comme les questions précédentes l' ont souligné, la prise en charge de Java pour le mappage mémoire n'est pas idéale et vous voudrez donc implémenter une bibliothèque JNI pour exécuter mmap.


3
Pourquoi JNI est-il lent? Considérez comment fonctionne la couche TCP de bas niveau en Java, elle n'est pas écrite en code octet Java! (Par exemple, cela doit passer par l'hôte natif.) Ainsi, je rejette l'affirmation selon laquelle les sockets Java TCP sont plus rapides que JNI. (JNI, cependant, n'est pas IPC.)

4
Un seul appel JNI vous coûte 9ns (sur un Intel i5) si vous n'utilisez que des primitives. Ce n'est donc pas si lent.
Martin Kersten
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.