Os methods inline Java durante a otimização?

Gostaria de saber se a JVM / javac é inteligente o suficiente para transformar

// This line... string a = foo(); string foo() { return bar(); } string bar() { return some-complicated-string computation; } 

para dentro

 string a = bar(); 

Ou desmarque a chamada desnecessária para foo () no caso de liberação (porque código inacessível):

 string a = foo(bar()); // bar is the same ... string foo(string b) { if (debug) do-something-with(b); } 

Meu sentimento é sim para o primeiro exemplo e “não tão certo” para o segundo, mas alguém poderia me dar alguns pointers / links para confirmar isso?

javac apresentará o bytecode que é uma representação fiel do programa Java original que gerou o bytecode (exceto em certas situações em que ele pode otimizar: eliminação constante de código e código morto ). No entanto, a otimização pode ser executada pela JVM quando ela usa o compilador JIT.

Para o primeiro cenário, parece que a JVM suporta inlining (veja em Métodos aqui e veja aqui um exemplo inlining na JVM).

Eu não encontrei nenhum exemplo do método inlining sendo executado pelo próprio javac . Eu tentei compilar alguns programas de exemplo (semelhante ao que você descreveu na sua pergunta) e nenhum deles parecia diretamente inline o método, mesmo quando era final . Parece que esse tipo de otimização é feito pelo compilador JIT da JVM e não pelo javac . O “compilador” mencionado em Métodos aqui parece ser o compilador JIT do HotSpot JVM e não o javac .

Pelo que vejo, o javac suporta a eliminação de código morto (veja o exemplo para o segundo caso) e a dobra constante . Na dobra constante, o compilador irá pré-calcular expressões constantes e usar o valor calculado em vez de executar o cálculo durante o tempo de execução. Por exemplo:

 public class ConstantFolding { private static final int a = 100; private static final int b = 200; public final void baz() { int c = a + b; } } 

compila para o seguinte bytecode:

 Compiled from "ConstantFolding.java" public class ConstantFolding extends java.lang.Object{ private static final int a; private static final int b; public ConstantFolding(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return public final void baz(); Code: 0: sipush 300 3: istore_1 4: return } 

Note que o bytecode tem um sipush 300 vez de getfield s e um iadd . 300 é o valor calculado. Este é também o caso das variables private final . Se a e b não forem estáticos, o bytecode resultante será:

 Compiled from "ConstantFolding.java" public class ConstantFolding extends java.lang.Object{ private final int a; private final int b; public ConstantFolding(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: aload_0 5: bipush 100 7: putfield #2; //Field a:I 10: aload_0 11: sipush 200 14: putfield #3; //Field b:I 17: return public final void baz(); Code: 0: sipush 300 3: istore_1 4: return } 

Aqui também, um sipush 300 é usado.

Para o segundo caso (eliminação de código morto), usei o seguinte programa de teste:

 public class InlineTest { private static final boolean debug = false; private void baz() { if(debug) { String a = foo(); } } private String foo() { return bar(); } private String bar() { return "abc"; } } 

que dá o seguinte bytecode:

 Compiled from "InlineTest.java" public class InlineTest extends java.lang.Object{ private static final boolean debug; public InlineTest(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return private void baz(); Code: 0: return private java.lang.String foo(); Code: 0: aload_0 1: invokespecial #2; //Method bar:()Ljava/lang/String; 4: areturn private java.lang.String bar(); Code: 0: ldc #3; //String abc 2: areturn } 

Como você pode ver, o foo não é chamado de todo em baz porque o código dentro do bloco if está efetivamente “morto”.

A JVM HotSpot da Sun (agora da Oracle) combina a interpretação do bytecode, bem como a compilation JIT. Quando o bytecode é apresentado à JVM, o código é inicialmente interpretado, mas a JVM monitora o código de bytes e seleciona as partes que são executadas com frequência. Ele cobre essas partes em código nativo para que elas sejam executadas mais rapidamente. Por pedaço de bytecode que não são usados ​​com tanta freqüência, essa compilation não é feita. Isso é bom porque a compilation tem alguma sobrecarga. Então é realmente uma questão de tradeoff. Se você decidir compilar todo bytecode para nativecode, então o código pode ter um atraso de boot muito longo.

Além de monitorar o bytecode, a JVM também pode executar a análise estática do bytecode enquanto o interpreta e carrega para executar uma otimização adicional.

Se você deseja conhecer os tipos específicos de otimizações que a JVM executa, esta página no Oracle é bastante útil. Descreve as técnicas de desempenho utilizadas na JVM HotSpot.

no mesmo arquivo de class, o javac poderá inline static e final (outros arquivos de class podem alterar a function inline)

no entanto, o JIT será capaz de otimizar muito mais (incluindo inlining overfluous removendo limites e verificações de nulos, etc.) porque ele sabe mais sobre o código

Um compilador JIT “altamente otimizado” irá embutir ambos os casos (e, @Mysticial, pode até mesmo inline alguns casos polimórficos, empregando várias formas de truques).

Você pode aumentar as chances de criar methods finais e alguns outros truques.

O javac faz alguns inlining primitivos, principalmente de methods finais / privados, destinados principalmente a ajudar alguns paradigmas de compilation condicional.

A JVM provavelmente será inline. Em geral, é melhor otimizar para a legibilidade humana. Deixe a JVM fazer a otimização do tempo de execução.

O especialista em JVM, Brian Goetz, diz que a final não tem nenhum impacto sobre os methods que estão sendo embutidos.

Se você lançar uma exceção no bar () e imprimir o stacktrace, verá o caminho completo das chamadas … Acho que o java honra todas elas.

O segundo caso é o mesmo, a debugging é apenas uma variável do seu sistema, não uma definição como em C ++, portanto, é obrigatório avaliá-lo antes.

Eu posso estar errado, mas meu sentimento é “não em todos os casos”. Porque sua string bar() pode ser sobrescrita por sobrecarga de outras classs no mesmo pacote. final methods final são bons candidatos, mas depende do JIT.

Outra nota interessante é aqui .