Compilação antes do tempo (AoT)

Compilação antes do tempo (AoT)

1. Introdução

Neste artigo, veremos o compilador Java Ahead of Time (AOT), que é descrito emJEP-295e foi adicionado como um recurso experimental em Java 9.

Primeiro, veremos o que é AOT e, em segundo lugar, veremos um exemplo simples. Terceiro, veremos algumas restrições do AOT e, por último, discutiremos alguns possíveis casos de uso.

2. O que é compilação antecipada?

AOT compilation is one way of improving the performance of Java programs and in particular the startup time of the JVM. A JVM executa o bytecode Java e compila o código executado com frequência no código nativo. Isso é chamado de compilação Just-in-Time (JIT). A JVM decide qual código compilar o JIT com base nas informações de criação de perfil coletadas durante a execução.

Embora essa técnica permita que a JVM produza código altamente otimizado e melhore o desempenho máximo, o tempo de inicialização provavelmente não é ideal, pois o código executado ainda não foi compilado pelo JIT. AOT aims to improve this so-called warming-up period. O compilador usado para AOT é Graal.

In this article, we won’t look at JIT and Graal in detail. Consulte nossos outros artigos para uma visão geral deperformance improvements in Java 9 and 10, bem comodeep dive into the Graal JIT Compiler.

3. Exemplo

Para este exemplo, vamos usar uma classe muito simples, compilá-la e ver como usar a biblioteca resultante.

3.1. Compilação AOT

Vamos dar uma olhada rápida em nosso exemplo de classe:

public class JaotCompilation {

    public static void main(String[] argv) {
        System.out.println(message());
    }

    public static String message() {
        return "The JAOT compiler says 'Hello'";
    }
}

Antes de podermos usar o compilador AOT, precisamos compilar a classe com o compilador Java:

javac JaotCompilation.java

Em seguida, passamos oJaotCompilation.class resultante para o compilador AOT, que está localizado no mesmo diretório que o compilador Java padrão:

jaotc --output jaotCompilation.so JaotCompilation.class

Isso produz a bibliotecajaotCompilation.so no diretório atual.

3.2. Executando o Programa

Podemos então executar o programa:

java -XX:AOTLibrary=./jaotCompilation.so JaotCompilation

O argumento-XX:AOTLibrary aceita um caminho relativo ou completo para a biblioteca. Como alternativa, podemos copiar a biblioteca para a pastalib no diretório inicial do Java e passar apenas o nome da biblioteca.

3.3. Verificando se a biblioteca é chamada e usada

Podemos ver que a biblioteca foi realmente carregada adicionando-XX:+PrintAOT como um argumento JVM:

java -XX:+PrintAOT -XX:AOTLibrary=./jaotCompilation.so JaotCompilation

A saída será semelhante a:

77    1     loaded    ./jaotCompilation.so  aot library

No entanto, isso nos diz apenas que a biblioteca foi carregada, mas não que ela realmente foi usada. Ao passar o argumento-verbose, podemos ver que os métodos na biblioteca são realmente chamados:

java -XX:AOTLibrary=./jaotCompilation.so -verbose -XX:+PrintAOT JaotCompilation

A saída conterá as linhas:

11    1     loaded    ./jaotCompilation.so  aot library
116    1     aot[ 1]   jaotc.JaotCompilation.()V
116    2     aot[ 1]   jaotc.JaotCompilation.message()Ljava/lang/String;
116    3     aot[ 1]   jaotc.JaotCompilation.main([Ljava/lang/String;)V
The JAOT compiler says 'Hello'

A biblioteca compilada AOT contém umclass fingerprint, que deve corresponder à impressão digital do arquivo.class.

Vamos mudar o código na classeJaotCompilation.java para retornar uma mensagem diferente:

public static String message() {
    return "The JAOT compiler says 'Good morning'";
}

Se executarmos o programa sem AOT compilar a classe modificada:

java -XX:AOTLibrary=./jaotCompilation.so -verbose -XX:+PrintAOT JaotCompilation

Então a saída conterá apenas:

 11 1 loaded ./jaotCompilation.so aot library
The JAOT compiler says 'Good morning'

We can see that the methods in the library won’t be called, as the bytecode of the class has changed. A ideia por trás disso é que o programa sempre produzirá o mesmo resultado, não importa se uma biblioteca compilada AOT está carregada ou não.

4. Mais argumentos de AOT e JVM

4.1. Compilação AOT de módulos Java

Também é possível compilar AOT um módulo:

jaotc --output javaBase.so --module java.base

A biblioteca resultantejavaBase.so tem cerca de 320 MB e leva algum tempo para carregar. O tamanho pode ser reduzido selecionando os pacotes e classes a serem compilados no AOT.

Veremos como fazer isso a seguir, no entanto, não vamos nos aprofundar em todos os detalhes.

4.2. Compilação seletiva com comandos de compilação

To prevent the AOT compiled library of a Java module from becoming too large, we can add compile commands to limit the scope of what gets AOT compiled. Esses comandos precisam estar em um arquivo de texto - em nosso exemplo, usaremos o arquivocomplileCommands.txt:

compileOnly java.lang.*

Em seguida, adicionamos ao comando compile:

jaotc --output javaBaseLang.so --module java.base --compile-commands compileCommands.txt

A biblioteca resultante conterá apenas as classes compiladas AOT empackage java.lang.

To gain real performance improvement, we need to find out which classes are invoked during the warm-up of the JVM.

Isso pode ser alcançado adicionando vários argumentos da JVM:

java -XX:+UnlockDiagnosticVMOptions -XX:+LogTouchedMethods -XX:+PrintTouchedMethodsAtExit JaotCompilation

Neste artigo, não vamos nos aprofundar nessa técnica.

4.3. Compilação AOT de uma única classe

Podemos compilar uma única classe com o argumento–class-name:

jaotc --output javaBaseString.so --class-name java.lang.String

A biblioteca resultante conterá apenas a classeString.

4.4. Compilar para camadas

Por padrão, o código compilado AOT sempre será usado e nenhuma compilação JIT ocorrerá para as classes incluídas na biblioteca. If we want to include the profiling information in the library, we can add the argument compile-for-tiered:

jaotc --output jaotCompilation.so --compile-for-tiered JaotCompilation.class

O código pré-compilado na biblioteca será usado até que o bytecode se torne elegível para a compilação JIT.

5. Possíveis casos de uso para compilação AOT

Um caso de uso do AOT são os programas de execução curta, que concluem a execução antes que ocorra qualquer compilação JIT.

Outro caso de uso são ambientes incorporados, onde JIT não é possível.

Neste ponto, também precisamos observar que a biblioteca compilada AOT só pode ser carregada de uma classe Java com bytecode idêntico, portanto, não pode ser carregada via JNI.

6. Conclusão

Neste artigo, vimos como AOT compilar classes e módulos Java. Como este ainda é um recurso experimental, o compilador AOT não faz parte de todas as distribuições. Ainda é raro encontrar exemplos reais, e caberá à comunidade Java encontrar os melhores casos de uso para aplicar o AOT.

Todos os trechos de código neste artigo podem ser encontrados em nossoGitHub repository.