Usando arquivos para o IPC de memória compartilhada, o mapeamento de memória é um requisito?

votos
19

Existem alguns projetos por aí que utilizam os MappedByteBuffers retornados pelo FileChannel.map() do Java como uma forma de ter IPCs de memória compartilhada entre JVMs no mesmo host (veja Chronicle Queue, Aeron IPC, etc.). Tanto quanto eu posso dizer, esta api fica em cima da chamada mmap. Entretanto, a implementação do Java não permite mapeamentos anônimos (sem suporte a arquivos).

Minha pergunta é, em Java (1.8) e Linux (3.10), os MappedByteBuffers são realmente necessários para implementar IPC de memória compartilhada, ou qualquer acesso a um arquivo comum forneceria a mesma funcionalidade? (Esta pergunta não se refere à implicação de desempenho do uso ou não de um MappedByteBuffer)

Aqui está o meu entendimento:

  1. Quando o Linux carrega um arquivo do disco, ele copia o conteúdo desse arquivo para páginas na memória. Essa região da memória é chamada de cache de páginas. Até onde posso dizer, ela faz isso independentemente do método Java (FileInputStream.read(), RandomAccessFile.read(), FileChannel.read(), FileChannel.map()) ou método nativo é usado para ler o arquivo (obsevado com free e monitorando o valor cache).
  2. Se outro processo tentar carregar o mesmo arquivo (enquanto ele ainda estiver residente no cache) o kernel detecta isso e não precisa recarregar o arquivo. Se a cache da página ficar cheia, as páginas serão despejadas - as que estiverem sujas serão escritas de volta para o disco. (Páginas também são escritas de volta se houver uma descarga explícita no disco, e periodicamente, com uma thread do kernel).
  3. Ter um arquivo (grande) já no cache é um aumento de desempenho significativo, muito mais do que as diferenças baseadas em quais métodos Java usamos para abrir/ler esse arquivo.
  4. Um programa em C chamando o sistema de chamada mmap pode fazer um mapeamento ANÓNIMO, que essencialmente aloca páginas no cache que não são suportadas por um arquivo real (então não há necessidade de emitir gravações reais no disco), mas Java não parece oferecer isso (o mapeamento de um arquivo em tmpfs realizaria a mesma coisa?)
  5. Se um arquivo é carregado usando a chamada de sistema mmap (C) ou via FileChannel.map() (Java), essencialmente as páginas do arquivo (na cache) são carregadas diretamente no espaço de endereços do processo. Usando outros métodos para abrir um arquivo, o arquivo é carregado em páginas que não estão no espaço de endereços do processo, e então os vários métodos para ler/escrever esse arquivo copiam alguns bytes de/para essas páginas em um buffer no espaço de endereços do processo. Há um benefício óbvio de desempenho evitando essa cópia, mas minha pergunta não está relacionada ao desempenho.

Então, em resumo, se eu entendi corretamente - enquanto o mapeamento oferece uma vantagem de desempenho, ele não parece oferecer nenhuma funcionalidade de memória compartilhada que nós ainda não obtemos apenas da natureza do Linux e do cache da página.

Então, por favor, diz-me onde está a minha compreensão.

Obrigado.

Publicado 22/05/2020 em 21:20
fonte usuário
Em outras línguas...                            


2 respostas

votos
0

Vale a pena mencionar três pontos: desempenho, e mudanças simultâneas, e utilização de memória.

Você está correto na avaliação de que o MMAP normalmente oferecerá vantagem de desempenho sobre o IO baseado em arquivos. Em particular, a vantagem de desempenho é significativa se o código executar muitas pequenas IO no ponto artbitrary do arquivo.

considere mudar o N-ésimo byte: com mmap buffer[N] = buffer[N] + 1, e com acesso baseado em arquivo você precisa (pelo menos) de 4 chamadas de sistema de verificação de erros:

   seek() + error check
   read() + error check
   update value
   seek() + error check
   write + error check

É verdade que o número de IO reais (para o disco) provavelmente é o mesmo.

O segundo ponto que vale a pena mencionar é o acesso simultâneo. Com IO baseado em arquivo, você tem que se preocupar com o acesso simultâneo em potencial. Você precisará emitir bloqueio explícito (antes da leitura), e desbloquear (após a gravação), para evitar dois processos por acessar incorretamente o valor ao mesmo tempo. Com a memória partilhada, as operações atómicas podem eliminar a necessidade de bloqueio adicional.

O terceiro ponto é a utilização real da memória. Nos casos em que o tamanho dos objetos compartilhados é significativo, o uso de memória compartilhada pode permitir um grande número de processos para acessar os dados sem a alocação de memória adicional. Se os sistemas limitados pela memória, ou o sistema que precisa fornecer desempenho em tempo real, esta pode ser a única maneira de acessar os dados.

Respondeu 29/05/2020 em 10:35
fonte usuário

votos
0

Minha pergunta é, em Java (1.8) e Linux (3.10), os MappedByteBuffers são realmente necessários para implementar IPC de memória compartilhada, ou qualquer acesso a um arquivo comum forneceria a mesma funcionalidade?

Depende da razão pela qual você quer implementar IPC de memória compartilhada.

Você pode claramente implementar IPC sem memória compartilhada; por exemplo, sobre soquetes. Portanto, se você não estiver fazendo isso por razões de desempenho, não é necessário fazer IPC de memória compartilhada de forma alguma!

Portanto, o desempenho tem de estar na raiz de qualquer discussão.

O acesso usando arquivos através das APIs Java classic io ou nio não fornece funcionalidade ou desempenho de memória compartilhada.

A principal diferença entre E/S de arquivo regular ou E/S de soquete versus IPC de memória compartilhada é que o primeiro requer que as aplicações façam reade writesyscalls explicitamente para enviar e receber mensagens. Isto implica em syscalls extra, e implica a cópia de dados do kernel. Além disso, se há múltiplas threads você precisa de um "canal" separado entre cada par de threads ou algo para multiplexar múltiplas "conversas" em um canal compartilhado. Este último pode fazer com que o canal compartilhado se torne um gargalo de estrangulamento concomitante.

Note que estas despesas gerais são ortogonais para a cache de páginas do Linux.

Por outro lado, com o IPC implementado usando memória compartilhada, não readhá nenhum e writesyscalls, e nenhum passo extra de cópia. Cada "canal" pode simplesmente usar uma área separada do buffer mapeado. Um thread em um processo grava os dados na memória compartilhada e é quase imediatamente visível para o segundo processo.

A ressalva é que os processos precisam 1) sincronizar, e 2) implementar barreiras de memória para garantir que o leitor não veja dados obsoletos. Mas ambas podem ser implementadas sem syscalls.

Na lavagem, a memória compartilhada IPC usando arquivos mapeados de memória >> é<< mais rápida do que usar arquivos ou soquetes convencionais, e é por isso que as pessoas fazem isso.


Você também perguntou implicitamente se o IPC de memória compartilhada pode ser implementado sem arquivos mapeados de memória.

  • Uma maneira prática seria criar um arquivo mapeado por memória para um arquivo que vive em um sistema de arquivos somente de memória; por exemplo, um "tmpfs" no Linux.

    Tecnicamente, isso ainda é um arquivo com a memória mapeada. No entanto, você não incorre em despesas gerais de descarga de dados para o disco e evita a potencial preocupação de segurança de dados IPC privados que acabam em disco.

  • Você poderia, em teoria, implementar um segmento compartilhado entre dois processos, fazendo o seguinte:

    • No processo pai, use o mmap para criar um segmento com MAP_ANONYMOUS | MAP_SHARED.
    • Fork processos infantis. Estes acabarão compartilhando o segmento uns com os outros e o processo pai.

    Entretanto, implementar isso para um processo Java seria ... desafiador. AFAIK, Java não suporta isso.

Referência:

Respondeu 31/05/2020 em 06:17
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more