método mais rápido (baixa latência) para comunicação entre processos entre Java e C / C ++

100

Tenho um aplicativo Java, conectando-se através de soquete TCP a um "servidor" desenvolvido em C / C ++.

o aplicativo e o servidor estão rodando na mesma máquina, uma caixa Solaris (mas estamos considerando migrar para o Linux eventualmente). tipo de dados trocados são mensagens simples (login, login ACK, então o cliente pede algo, o servidor responde). cada mensagem tem cerca de 300 bytes.

Atualmente estamos usando Sockets, e está tudo bem, porém estou procurando uma forma mais rápida de trocar dados (menor latência), usando métodos IPC.

Tenho pesquisado na Internet e encontrei referências às seguintes tecnologias:

  • memoria compartilhada
  • tubos
  • filas
  • bem como o que é referido como DMA (Direct Memory Access)

mas não consegui encontrar uma análise adequada de seus respectivos desempenhos, nem como implementá-los em JAVA e C / C ++ (para que eles possam se comunicar), exceto talvez pipes que eu poderia imaginar como fazer.

Alguém pode comentar sobre o desempenho e a viabilidade de cada método neste contexto? qualquer indicador / link para informações úteis de implementação?


EDITAR / ATUALIZAR

seguindo o comentário e as respostas que obtive aqui, encontrei informações sobre os soquetes de domínio Unix, que parecem ser construídos sobre tubos, e me salvariam toda a pilha TCP. é específico da plataforma, então pretendo testá-lo com JNI ou juds ou junixsocket .

os próximos passos possíveis seriam a implementação direta de tubos e, em seguida, memória compartilhada, embora eu tenha sido avisado sobre o nível extra de complexidade ...


Obrigado pela ajuda

Bastien
fonte
7
Pode ser um exagero no seu caso, mas considere zeromq.org
jfs
isso é interessante, no entanto, a ideia seria usar métodos "genéricos" (como nos fornecidos pelo sistema operacional ou pela linguagem) primeiro, é por isso que mencionei as filas e a memória compartilhada.
Bastien
2
Consulte também stackoverflow.com/questions/904492
MSalters
Não se esqueça dos arquivos mapeados ou apenas UDP.
10
UDP mais lento que TCP ??? hmmm ... prova, por favor
Boppity Bop

Respostas:

103

Acabei de testar a latência do Java no meu Corei5 de 2,8 GHz, envio / recebimento de apenas um byte, 2 processos Java recém-gerados, sem atribuir núcleos de CPU específicos ao conjunto de tarefas:

TCP         - 25 microseconds
Named pipes - 15 microseconds

Agora especificando explicitamente as máscaras principais, como taskset 1 java Srv ou 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 !!!!

tão

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

Ao mesmo tempo, Thread.sleep (0) (que como mostra o strace faz com que uma única chamada de kernel do Linux sched_yield () seja executada) leva 0,3 microssegundo - assim, os pipes nomeados agendados para um único núcleo ainda têm muita sobrecarga

Algumas medidas de memória compartilhada: 14 de setembro de 2009 - Solace Systems anunciou hoje que sua API de plataforma de mensagem unificada pode atingir uma latência média de menos de 700 nanossegundos usando um transporte de memória compartilhada. http://solacesystems.com/news/fastest-ipc-messaging/

PS - tentei memória compartilhada no dia seguinte na forma de arquivos mapeados de memória, se a espera ocupada for aceitável, podemos reduzir a latência para 0,3 microssegundo para passar um único byte com 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) é necessário para que 2 processos possam ver as alterações um do outro (não conheço outra maneira ainda). Se 2 processos forçados ao mesmo núcleo com o conjunto de tarefas, a latência torna-se 1,5 microssegundos - isso é um atraso de mudança de contexto

PPS - e 0,3 microssegundo é um bom número! O código a seguir leva exatamente 0,1 microssegundo, ao fazer apenas uma concatenação de string primitiva:

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

PPPS - espero que isso não seja muito fora do assunto, mas finalmente tentei substituir Thread.sleep (0) com o incremento de uma variável int estática volátil (JVM acontece de liberar caches de CPU ao fazer isso) e obtido - registro! - Comunicação do processo java-para-java com latência de 72 nanossegundos !

Quando forçados ao mesmo núcleo de CPU, no entanto, JVMs de incremento volátil nunca fornecem controle um ao outro, produzindo assim exatamente 10 milissegundos de latência - o quantum de tempo do Linux parece ser 5 ms ... Portanto, deve ser usado apenas se houver um núcleo sobressalente - caso contrário, dormir (0) é mais seguro.

Andriy
fonte
obrigado Andriy, muito estudo de informação, e está correspondendo mais ou menos às minhas medições para TCP, então essa é uma boa referência. Acho que vou procurar tubos nomeados.
Bastien
Portanto, substituir o Thread (Sleep) pelo incremento do int estático volátil só deve ser feito se você puder fixar um processo em núcleos diferentes? Além disso, eu não sabia que você poderia fazer isso? Achei que o SO decidisse?
mezamórfico
3
Tente LockSupport.parkNanos (1), deve fazer a mesma coisa.
reccles
Muito agradável. Você pode fazer melhor (como em latência RTT de 5-7us) para ping TCP. Veja aqui: psy-lob-saw.blogspot.com/2012/12/…
Nitsan Wakart
1
Exploração adicional do uso de arquivo mapeado de memória como memória compartilhada para suportar a fila IPC em Java: psy-lob-saw.blogspot.com/2013/04/lock-free-ipc-queue.html alcançando 135 milhões de mensagens por segundo. Veja também minha resposta abaixo para um estudo comparativo de latência por método.
Nitsan Wakart de
10

DMA é um método pelo qual os dispositivos de hardware podem acessar a RAM física sem interromper a CPU. Por exemplo, um exemplo comum é um controlador de disco rígido que pode copiar bytes direto do disco para a RAM. Como tal, não é aplicável ao IPC.

A memória compartilhada e os canais são suportados diretamente por sistemas operacionais modernos. Como tal, são bastante rápidos. Filas são normalmente abstrações, por exemplo, implementadas em cima de soquetes, tubos e / ou memória compartilhada. Isso pode parecer um mecanismo mais lento, mas a alternativa é que você crie essa abstração.

MSalters
fonte
para DMA, por que então posso ler um monte de coisas relacionadas a RDMA (como Remote Direct Memory Access) que se aplicariam em toda a rede (especialmente com InfiniBand) e fazer a mesma coisa. Na verdade estou tentando conseguir o equivalente SEM a rede (já que está tudo na mesma caixa).
Bastien
RDMA é o mesmo conceito: copiar bytes em uma rede sem interromper as CPUs em nenhum dos lados. Ele ainda não opera no nível do processo.
MSalters
10

A pergunta foi feita há algum tempo, mas você pode estar interessado em https://github.com/peter-lawrey/Java-Chronicle que suporta latências típicas de 200 ns e taxas de transferência de 20 M mensagens / segundo. Ele usa arquivos mapeados de memória compartilhados entre processos (também persiste os dados, o que torna a maneira mais rápida de persistir os dados)

Peter Lawrey
fonte
7

Este é um projeto que contém testes de desempenho para vários transportes IPC:

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

Sustrik
fonte
Não inclui o 'fator Java', mas parece interessante.
6

Se você já pensou em usar o acesso nativo (já que seu aplicativo e o "servidor" estão na mesma máquina), considere JNA , ele tem menos código clichê para você lidar.

Bakkal
fonte
6

Chegou tarde, mas queria apontar um projeto de código aberto dedicado a medir a latência de ping usando Java NIO.

Mais explorado / explicado nesta postagem do blog . Os resultados são (RTT em 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

Isso está de acordo com as linhas da resposta aceita. O erro System.nanotime () (estimado sem medir nada) é medido em cerca de 40 nanos, portanto, para o IPC, o resultado real pode ser menor. Aproveitar.

Nitsan Wakart
fonte
2

Não sei muito sobre comunicação nativa entre processos, mas acho que você precisa se comunicar usando código nativo, que pode ser acessado usando mecanismos JNI. Portanto, em Java, você chamaria uma função nativa que se comunica com o outro processo.

peixe
fonte
1

Na minha antiga empresa trabalhávamos com este projeto, http://remotetea.sourceforge.net/ , muito fácil de entender e integrar.

Seffi
fonte
0

Você já pensou em manter os soquetes abertos para que as conexões possam ser reutilizadas?

Thorbjørn Ravn Andersen
fonte
os soquetes permanecem abertos. a conexão permanece ativa durante todo o tempo de execução do aplicativo (cerca de 7 horas). as mensagens são trocadas mais ou menos continuamente (digamos cerca de 5 a 10 por segundo). a latência atual é de cerca de 200 microssegundos, o objetivo é reduzir 1 ou 2 ordens de magnitude.
Bastien
Uma latência de 2 ms? Ambicioso. Seria viável reescrever o material C em uma biblioteca compartilhada com a qual você possa interagir usando JNI?
Thorbjørn Ravn Andersen
2ms é 2.000 microssegundos, não 200. isso torna 2ms muito menos ambicioso.
thewhiteambit
-1

Relatório de bug da Oracle sobre o desempenho JNI: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4096069

JNI é uma interface lenta e, portanto, os soquetes Java TCP são o método mais rápido para notificação entre aplicativos; no entanto, isso não significa que você precisa enviar a carga útil por um soquete. Use LDMA para transferir a carga útil, mas, como as questões anteriores apontaram, o suporte Java para mapeamento de memória não é ideal e, portanto, você desejará implementar uma biblioteca JNI para executar o mmap.

Steve-o
fonte
3
Por que o JNI é lento? Considere como a camada TCP de baixo nível em Java funciona, ela não é escrita em código de bytes Java! (Por exemplo, isso precisa passar pelo host nativo.) Portanto, rejeito a afirmação de que os soquetes Java TCP são mais rápidos do que o JNI. (JNI, no entanto, não é IPC.)
4
Uma única chamada JNI custa 9 ns (em um Intel i5) se você usar apenas primitivos. Portanto, não é tão lento.
Martin Kersten