O que torna as chamadas JNI lentas?

194

Eu sei que 'ultrapassar fronteiras' ao fazer uma chamada JNI em Java é lento.

No entanto, eu quero saber o que é que torna mais lento? O que a implementação subjacente da jvm faz ao fazer uma chamada JNI que a torna tão lenta?

pdeva
fonte
2
(+1) Boa pergunta. Enquanto estamos no assunto, gostaria de incentivar qualquer pessoa que tenha feito benchmarks reais para publicar suas descobertas.
NPE
2
Uma chamada JNI precisa converter os objetos Java passados ​​para algo que C (por exemplo) possa entender; mesmo com o valor de retorno. A conversão de tipos e a organização da pilha de chamadas são uma boa parte.
Dave Newton
Dave, eu entendo e já ouvi falar disso antes. Mas como é exatamente a conversão? o que é isso 'alguma coisa'? Estou procurando detalhes.
Pdeva # 8/11
O uso de ByteBuffers diretos para transmitir dados entre Java e C pode resultar em sobrecarga relativamente baixa.
Peter Peterrey
6
a chamada precisa de um quadro de pilha C adequado, pressionando todos os registros úteis da CPU (e devolvendo-os), a chamada precisa de cercas e também evita muitas otimizações, como inline. Além disso, os encadeamentos precisam deixar o bloqueio da pilha de execução (por exemplo, para permitir que bloqueios tendenciosos funcionem enquanto estiverem no código nativo) e depois recuperá-lo.
bestsss

Respostas:

174

Primeiro, vale a pena notar que, "devagar", estamos falando de algo que pode levar dezenas de nanossegundos. Para métodos nativos triviais, em 2010 medi as chamadas em uma média de 40 ns na área de trabalho do Windows e 11 ns na área de trabalho do Mac. A menos que você esteja fazendo muitas ligações, você não notará.

Dito isto, chamar um método nativo pode ser mais lento do que fazer uma chamada de método Java normal. As causas incluem:

  • Os métodos nativos não serão incorporados pela JVM. Nem serão compilados just-in-time para esta máquina específica - eles já estão compilados.
  • Uma matriz Java pode ser copiada para acesso no código nativo e posteriormente copiada. O custo pode ser linear no tamanho da matriz. Avaliei a cópia JNI de uma matriz de 100.000 para obter uma média de 75 microssegundos na área de trabalho do Windows e 82 microssegundos no Mac. Felizmente, o acesso direto pode ser obtido via GetPrimitiveArrayCritical ou NewDirectByteBuffer .
  • Se o método receber um objeto ou precisar fazer um retorno de chamada, o método nativo provavelmente fará suas próprias chamadas para a JVM. O acesso a campos, métodos e tipos Java a partir do código nativo requer algo semelhante à reflexão. As assinaturas são especificadas em cadeias e consultadas na JVM. Isso é lento e propenso a erros.
  • Java Strings são objetos, têm comprimento e são codificados. Acessar ou criar uma string pode exigir uma cópia O (n).

Alguma discussão adicional, possivelmente datada, pode ser encontrada em "Desempenho da plataforma Java¿: estratégias e táticas", 2000, por Steve Wilson e Jeff Kesselman, na seção "9.2: Examinando custos JNI". É cerca de um terço do caminho nesta página , fornecido no comentário por @Philip abaixo.

O documento IBM developerWorks de 2009 "Práticas recomendadas para usar a Java Native Interface" fornece algumas sugestões sobre como evitar armadilhas de desempenho com a JNI.

Andy Thomas
fonte
1
Esta resposta afirma que algum código nativo pode ser incorporado pela JVM.
AH
5
Essa resposta observa que algum código nativo padrão está embutido na JVM em vez de usar a JNI. Acima, "métodos nativos" refere-se ao caso geral de métodos nativos definidos pelo usuário implementados via JNI. Obrigado pelo ponteiro para sun.misc.Unsafe.
Andy Thomas
Eu não queria afirmar que essa abordagem pode ser usada para todas as chamadas JNI. Mas não vai doer saber que há é um meio-termo entre bytecode pura e código JNI puro. Talvez isso afete algumas decisões de design. Talvez esse mecanismo seja generalizado no futuro.
AH
3
@ Ah, você confunde intrínseco w / JNI. Eles são bem diferentes. sun.misc.Unsafee muitas outras coisas System.currentTimeMillis/nanoTimesão tratadas via 'magic' pela JVM. Eles não são JNI e não possuem arquivos .c / .h adequados, exceto o implemento da JVM. A abordagem não pode ser seguida, a menos que você esteja gravando / hackeando a JVM.
bestsss 18/01/12
1
" este documento java.sun.com " está quebrado - aqui está um link para trabalhar.
Philip Guin
25

Vale ressaltar que nem todos os métodos Java marcados com nativesão "lentos". Alguns deles são intrínsecos, o que os torna extremamente rápidos. Para verificar quais são intrínsecos e quais não, você pode procurar do_intrinsicem vmSymbols.hpp .

Tema
fonte
23

Basicamente, a JVM interpreta interpretativamente os parâmetros C para cada chamada JNI e o código não é otimizado.

Existem muitos outros detalhes descritos neste documento

Se você estiver interessado em comparar JNI versus código nativo, este projeto possui código para executar benchmarks.

dmck
fonte
2
o artigo ao qual você vinculou parece mais um documento de referência de desempenho do que aquele que descreve como a JNI funciona internamente.
Pdeva #
@pdeva Infelizmente, os outros recursos que encontrei foram vinculados ao java.sun.com e os links não foram atualizados desde a aquisição da Oracle. Estou procurando mais detalhes sobre os internos da JNI.
dmck
13
O artigo é sobre Java 1.3 - há muito tempo. Os problemas dessa época ainda se aplicam ao Java 7?
AH