El método más rápido (baja latencia) para la comunicación entre procesos entre Java y C / C ++

100

Tengo una aplicación Java, que se conecta a través de un socket TCP a un "servidor" desarrollado en C / C ++.

Tanto la aplicación como el servidor se ejecutan en la misma máquina, una caja Solaris (pero estamos considerando migrar a Linux eventualmente). El tipo de datos intercambiados son mensajes simples (inicio de sesión, ACK de inicio de sesión, luego el cliente pide algo, el servidor responde). cada mensaje tiene una longitud aproximada de 300 bytes.

Actualmente estamos usando Sockets y todo está bien, sin embargo, estoy buscando una forma más rápida de intercambiar datos (menor latencia), usando métodos IPC.

He estado investigando en la red y encontré referencias a las siguientes tecnologías:

  • memoria compartida
  • tubería
  • colas
  • así como lo que se conoce como DMA (acceso directo a memoria)

pero no pude encontrar un análisis adecuado de sus respectivos rendimientos, ni cómo implementarlos tanto en JAVA como en C / C ++ (para que puedan hablar entre sí), excepto tal vez tuberías que podría imaginar cómo hacerlo.

¿Alguien puede comentar sobre el rendimiento y la viabilidad de cada método en este contexto? ¿Algún puntero / enlace a información de implementación útil?


EDITAR / ACTUALIZAR

Siguiendo el comentario y las respuestas que obtuve aquí, encontré información sobre Unix Domain Sockets, que parecen estar construidos solo sobre tuberías, y me ahorrarían toda la pila TCP. es específico de la plataforma, así que planeo probarlo con JNI o juds o junixsocket .

Los siguientes pasos posibles serían la implementación directa de tuberías, luego la memoria compartida, aunque me han advertido del nivel adicional de complejidad ...


gracias por tu ayuda

Bastien
fuente
7
Puede ser excesivo en su caso, pero considere zeromq.org
jfs
eso es interesante, sin embargo, la idea sería usar métodos "genéricos" (como en el sistema operativo o en el idioma) primero, por eso mencioné las colas y la memoria compartida.
Bastien
2
Consulte también stackoverflow.com/questions/904492
MSalters
No olvide los archivos mapeados o simplemente UDP.
10
UDP más lento que TCP ??? hmmm ... prueba por favor
Boppity Bop

Respuestas:

103

Acabo de probar la latencia de Java en mi Corei5 2.8GHz, solo envío / recepción de un solo byte, 2 procesos Java recién generados, sin asignar núcleos de CPU específicos con el conjunto de tareas:

TCP         - 25 microseconds
Named pipes - 15 microseconds

Ahora especifica explícitamente máscaras centrales, como taskset 1 java Srv o taskset 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 !!!!

entonces

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

Al mismo tiempo, Thread.sleep (0) (que como muestra strace hace que se ejecute una única llamada al kernel de Linux sched_yield ()) tarda 0,3 microsegundos, por lo que las canalizaciones con nombre programadas para un solo núcleo todavía tienen mucha sobrecarga

Algunas medidas de memoria compartida: 14 de septiembre de 2009 - Solace Systems anunció hoy que su API de plataforma de mensajería unificada puede alcanzar una latencia promedio de menos de 700 nanosegundos utilizando un transporte de memoria compartida. http://solacesystems.com/news/fastest-ipc-messaging/

PD: probé la memoria compartida al día siguiente en forma de archivos mapeados en memoria, si la espera ocupada es aceptable, podemos reducir la latencia a 0.3 microsegundos para pasar un solo byte con un código como este:

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
}

Notas: Thread.sleep (0) es necesario para que 2 procesos puedan ver los cambios entre sí (todavía no conozco otra forma). Si 2 procesos se fuerzan al mismo núcleo con el conjunto de tareas, la latencia se convierte en 1,5 microsegundos, es un retraso de cambio de contexto

PPS: ¡y 0,3 microsegundos es un buen número! El siguiente código toma exactamente 0,1 microsegundos, mientras que solo realiza una concatenación de cadenas primitivas:

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

PPPS: espero que esto no sea demasiado fuera de tema, pero finalmente intenté reemplazar Thread.sleep (0) con el incremento de una variable int volátil estática (JVM descarga los cachés de la CPU al hacerlo) y obtuve: ¡registro! - 72 nanosegundos de latencia de comunicación de proceso de Java a Java .

Sin embargo, cuando se les obliga al mismo núcleo de CPU, las JVM de incremento volátil nunca ceden el control entre sí, por lo que producen una latencia de exactamente 10 milisegundos; el tiempo cuántico de Linux parece ser de 5 ms ... Por lo tanto, esto debe usarse solo si hay un núcleo de repuesto. de lo contrario, dormir (0) es más seguro.

Andriy
fuente
gracias Andriy, muy estudio de información, y coincide más o menos con mis medidas para TCP, así que es una buena referencia. Supongo que buscaré tuberías con nombre.
Bastien
Entonces, ¿reemplazar el Thread (Sleep) con incrementar el int estático volátil solo debe hacerse si puede anclar un proceso a diferentes núcleos? Además, ¿no me di cuenta de que podías hacer esto? ¿Pensé que el sistema operativo decide?
mezamórfico
3
Pruebe LockSupport.parkNanos (1), debería hacer lo mismo.
Reccles el
Muy agradable. Sin embargo, puede hacerlo mejor (como en la latencia RTT 5-7us) para el ping TCP. Ver aquí: psy-lob-saw.blogspot.com/2012/12/…
Nitsan Wakart
1
Exploración adicional del uso de archivos asignados en memoria como memoria compartida para admitir la cola de IPC en Java: psy-lob-saw.blogspot.com/2013/04/lock-free-ipc-queue.html logrando 135 millones de mensajes por segundo. También vea mi respuesta a continuación para un estudio comparativo de la latencia por método.
Nitsan Wakart
10

DMA es un método mediante el cual los dispositivos de hardware pueden acceder a la RAM física sin interrumpir la CPU. Por ejemplo, un ejemplo común es un controlador de disco duro que puede copiar bytes directamente del disco a la RAM. Como tal, no es aplicable a IPC.

La memoria compartida y las canalizaciones son compatibles directamente con los sistemas operativos modernos. Como tal, son bastante rápidos. Las colas son típicamente abstracciones, por ejemplo, implementadas sobre sockets, tuberías y / o memoria compartida. Esto puede parecer como un mecanismo más lento, pero la alternativa es que se crea una abstracción.

MSalters
fuente
para DMA, ¿por qué entonces puedo leer muchas cosas relacionadas con RDMA (como Remote Direct Memory Access) que se aplicarían a través de la red (especialmente con InfiniBand) y hacer lo mismo. De hecho, estoy tratando de lograr el equivalente SIN la red (ya que todo está en la misma caja).
Bastien
RDMA es el mismo concepto: copiar bytes a través de una red sin interrumpir las CPU en ninguno de los lados. Todavía no funciona a nivel de proceso.
MSalters
10

La pregunta se hizo hace algún tiempo, pero es posible que le interese https://github.com/peter-lawrey/Java-Chronicle, que admite latencias típicas de 200 ns y rendimientos de 20 M mensajes / segundo. Utiliza archivos mapeados en memoria compartidos entre procesos (también conserva los datos, lo que hace que sea la forma más rápida de conservar los datos)

Peter Lawrey
fuente
6

Si alguna vez considera usar el acceso nativo (ya que tanto su aplicación como el "servidor" están en la misma máquina), considere JNA , tiene menos código repetitivo con el que lidiar.

bakkal
fuente
6

Llegó tarde, pero quería señalar un proyecto de código abierto dedicado a medir la latencia del ping utilizando Java NIO.

Más explorado / explicado en esta publicación de blog . Los resultados son (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

Esto está en la línea de la respuesta aceptada. El error System.nanotime () (estimado sin medir nada) se mide en alrededor de 40 nanos, por lo que para el IPC el resultado real podría ser menor. Disfrutar.

Nitsan Wakart
fuente
2

No sé mucho sobre la comunicación nativa entre procesos, pero supongo que necesita comunicarse usando código nativo, al que puede acceder usando mecanismos JNI. Entonces, desde Java, llamaría a una función nativa que habla con el otro proceso.

pez
fuente
0

¿Ha considerado mantener los enchufes abiertos para que las conexiones se puedan reutilizar?

Thorbjørn Ravn Andersen
fuente
los enchufes permanecen abiertos. la conexión está activa durante todo el tiempo que se ejecuta la aplicación (alrededor de 7 horas). los mensajes se intercambian de forma más o menos continua (digamos de 5 a 10 por segundo). La latencia actual es de alrededor de 200 microsegundos, el objetivo es recortar 1 o 2 órdenes de magnitud.
Bastien
¿Una latencia de 2 ms? Ambicioso. ¿Sería factible reescribir el material C en una biblioteca compartida con la que pueda interactuar con JNI?
Thorbjørn Ravn Andersen
2ms son 2000 microsegundos, no 200. Esto hace que 2ms sea mucho menos ambicioso.
thewhiteambit
-1

Informe de errores de Oracle sobre el rendimiento de JNI: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4096069

JNI es una interfaz lenta y, por lo tanto, los sockets TCP de Java son el método más rápido para la notificación entre aplicaciones, sin embargo, eso no significa que tenga que enviar la carga útil a través de un socket. Use LDMA para transferir la carga útil, pero como han señalado las preguntas anteriores , el soporte de Java para el mapeo de memoria no es ideal y, por lo tanto, querrá implementar una biblioteca JNI para ejecutar mmap.

Steve-o
fuente
3
¿Por qué JNI es lento? Considere cómo funciona la capa TCP de bajo nivel en Java, ¡no está escrita en código de bytes de Java! (Por ejemplo, esto tiene que canalizarse a través del host nativo). Por lo tanto, rechazo la afirmación de que los sockets TCP de Java son más rápidos que JNI. (JNI, sin embargo, no es IPC.)
4
Una sola llamada JNI le costará 9ns (en un Intel i5) si solo usa primitivas. Entonces no es tan lento.
Martin Kersten