Ordenar arquivos

O uso de arquivos de ordenação é uma técnica recente para otimização de vinculadores. Esses são arquivos de texto contendo símbolos que representam funções. Vinculadores, como o lld, usam arquivos de ordenação para definir o layout das funções em uma ordem específica. Esses binários ou bibliotecas com símbolos ordenados reduzem as falhas da página e melhoram o tempo de inicialização de um programa devido ao carregamento eficiente de símbolos durante a inicialização a frio.

Os recursos de um arquivo de ordenação podem ser adicionados ao aplicativo seguindo três etapas:

  1. Gerar perfis e um arquivo de mapeamento
  2. Criar um arquivo de ordenação usando os perfis e o arquivo de mapeamento
  3. Usar o arquivo de ordenação durante o build de lançamento para definir o layout dos símbolos

Gerar um arquivo de ordenação

A geração de um arquivo de ordenação é feita em três etapas:

  1. Criar um build instrumentado do app que grave o arquivo de ordenação
  2. Executar o app para gerar os perfis
  3. Fazer o pós-processamento dos perfis e do arquivo de mapeamento

Criar um build instrumentado

Os perfis são gerados executando um build instrumentado do aplicativo. Um build instrumentado requer a adição de -forder-file-instrumentation às flags do compilador e do vinculador, com o -mllvm -orderfile-write-mapping=<filename>-mapping.txt sendo adicionado apenas às flags do compilador. A flag de instrumentação permite instrumentar o arquivo de ordenação para a criação de perfil e carrega a biblioteca específica necessária para essa criação. Por outro lado, a flag de mapeamento gera apenas o arquivo de mapeamento que mostra o hash MD5 de cada função no binário ou na biblioteca.

Além disso, você precisa transmitir todas as flags de otimização, exceto -O0, porque esse elemento é necessário para as flags de instrumentação e de mapeamento. Se nenhuma flag de otimização for transmitida, o arquivo de mapeamento não será gerado, e o build instrumentado poderá gerar hashes incorretos para o arquivo de perfil.

ndk-build

Crie com APP_OPTIM=release para que ndk-build use um modo de otimização diferente de -O0. Ao criar com o AGP, esse procedimento é automático para builds de lançamento.

LOCAL_CFLAGS += \
    -forder-file-instrumentation \
    -mllvm -orderfile-write-mapping=mapping.txt \

LOCAL_LDFLAGS += -forder-file-instrumentation

CMake

Use um CMAKE_BUILD_TYPE diferente de Debug para que o CMake utilize um modo de otimização diferente de -O0. Ao criar com o AGP, esse procedimento é automático para builds de lançamento.

target_compile_options(orderfiledemo PRIVATE
    -forder-file-instrumentation
    -mllvm -orderfile-write-mapping=mapping.txt
)
target_link_options(orderfiledemo PRIVATE -forder-file-instrumentation)

Outros sistemas de build

Compile seu código usando -forder-file-instrumentation -O1 -mllvm -orderfile-write-mapping=mapping.txt.

O uso de -O1 não é obrigatório, mas não utilize -O0.

Omita -mllvm -orderfile-write-mapping=mapping.txt durante a vinculação.

Todas essas flags não são necessárias para um build de lançamento, então ele precisa ser controlado por uma variante de build. Para simplificar, você pode configurar tudo isso no CMakeLists.txt, como mostrado neste exemplo (link em inglês).

Criar uma biblioteca de arquivos de ordenação

Além das flags, o arquivo de perfil precisa ser configurado, e o binário instrumentado precisa acionar explicitamente uma gravação de perfil durante a execução.

  • Chame __llvm_profile_set_filename(PROFILE_DIR "/<filename>-%m.profraw") para configurar o caminho do perfil. Embora o argumento transmitido seja <filename>-%m.profraw, o arquivo de perfil é salvo como <filename>-%m.profraw.order. O app precisa conseguir gravar no PROFILE_DIR, e você deve ter acesso ao diretório.
    • Como muitos perfis de bibliotecas compartilhadas são criados, %m é útil porque se expande para uma assinatura de módulo exclusiva para a biblioteca, resultando em um perfil separado por biblioteca. Para consultar mais especificadores de padrão, acesse este link (em inglês).
  • Chame __llvm_profile_initialize_file() para configurar o arquivo de perfil.
  • Chame __llvm_orderfile_dump() para gravar explicitamente no arquivo de perfil.

Os perfis são coletados na memória, e a função de despejo os grava no arquivo. Você precisa garantir que a função de despejo seja chamada no final da inicialização para que o arquivo de perfil tenha todos os símbolos até o final desse processo.

extern "C" {
extern int __llvm_profile_set_filename(const char*);
extern int __llvm_profile_initialize_file(void);
extern int __llvm_orderfile_dump(void);
}

#define PROFILE_DIR "<location-writable-from-app>"
void workload() {
  // ...
  // run workload
  // ...

  // set path and write profiles after workload execution
  __llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw");
  __llvm_profile_initialize_file();
  __llvm_orderfile_dump();
  return;
}

Executar o build para perfis

Execute o app instrumentado em um dispositivo físico ou virtual para gerar os perfis. Você pode extrair os arquivos de perfil usando adb pull.

adb shell "run-as <package-name> sh -c 'cat /data/user/0/<package-name>/cache/default-%m.profraw.order' | cat > /data/local/tmp/default-%m.profraw.order"
adb pull /data/local/tmp/default-%m.profraw.order .

Como mencionado, confira se você tem acesso à pasta que contém o arquivo de perfil gravado. Se você estiver usando um dispositivo virtual, recomendamos que evite os emuladores com a Play Store, porque eles não têm acesso a muitas pastas.

Pós-processar o arquivo de perfil e de mapeamento

Ao receber os perfis, você precisa encontrar o arquivo de mapeamento e converter cada perfil em um formato hexadecimal. Normalmente, o arquivo de mapeamento fica na pasta de build do app. Quando você tiver os dois, poderá usar nosso script para pegar um arquivo de perfil e o de mapeamento correto e gerar um arquivo de ordenação.

Linux/Mac/ChromeOS

hexdump -C default-%m.profraw.order > default-%m.prof
python3 create_orderfile.py --profile-file default-%m.prof --mapping-file <filename>-mapping.txt

Windows

certutil -f -encodeHex default-%m.profraw.order default-%m.prof
python3 create_orderfile.py --profile-file default-%m.prof --mapping-file <filename>-mapping.txt

Para saber mais sobre o script, consulte este arquivo README.

Usar o arquivo de ordenação para criar um aplicativo

Depois de gerar um arquivo de ordenação, remova as flags anteriores e as funções dele, já que elas são destinadas apenas a etapas de geração. Você só precisa transmitir -Wl,--symbol-ordering-file=<filename>.orderfile às flags de compilação e do vinculador. Às vezes, os símbolos não podem ser encontrados ou não podem ser movidos e emitem avisos para que você possa transmitir -Wl,--no-warn-symbol-ordering para suprimir esses avisos.

ndk-build

LOCAL_CFLAGS += \
    -Wl,--symbol-ordering-file=<filename>.orderfile \
    -Wl,--no-warn-symbol-ordering \

LOCAL_LDFLAGS += \
    -Wl,--symbol-ordering-file=<filename>.orderfile \
    -Wl,--no-warn-symbol-ordering \

CMake

target_compile_options(orderfiledemo PRIVATE
    -Wl,--symbol-ordering-file=<filename>.orderfile
    -Wl,--no-warn-symbol-ordering
)
target_link_options(orderfiledemo PRIVATE
    -Wl,--symbol-ordering-file=<filename>.orderfile
    -Wl,--no-warn-symbol-ordering
)

Outros sistemas de build

Compile seu código usando -Wl,--symbol-ordering-file=<filename>.orderfile -Wl,--no-warn-symbol-ordering.

Para mais informações, confira o exemplo de arquivo de ordenação (link em inglês).

Detalhes de implementação de arquivos de ordenação

Há muitas maneiras de gerar e usar arquivos de ordenação. O NDK usa o método do LLVM para que ele seja o mais útil para suas bibliotecas compartilhadas C ou C++ com um app Java ou Kotlin real. O Clang usa cada nome de função (símbolo) e cria um hash MD5 correspondente, gerando essa relação em um arquivo de mapeamento. O hash MD5 de uma função é gravado no arquivo de perfil (formato .profraw) quando a função é executada pela primeira vez. As execuções subsequentes não gravam o hash MD5 no arquivo de perfil para evitar cópias. Como resultado, apenas a primeira execução da função é registrada na ordem. Ao analisar o arquivo de perfil e o de mapeamento, você pode trocar cada hash MD5 pela função correspondente e gerar um arquivo de ordenação.

Os exemplos de um arquivo de perfil em formato hexadecimal e um de mapeamento podem ser encontrados como example.prof e example-mapping.txt, respectivamente.