Pengoptimalan yang dipandu profil

Pengoptimalan yang dipandu profil (Profile-guided optimization/PGO) adalah teknik pengoptimalan compiler yang sudah banyak dikenal. Dalam PGO, profil runtime dari eksekusi program digunakan oleh compiler untuk membuat pilihan yang optimal terkait inline dan tata letak kode. Hal ini dapat meningkatkan performa dan mengurangi ukuran kode.

PGO dapat di-deploy ke aplikasi atau library Anda dengan langkah-langkah berikut: 1. Mengidentifikasi beban kerja representatif. 2. Mengumpulkan profil. 3. Menggunakan profil di build Rilis.

Langkah 1: Mengidentifikasi Beban Kerja Representatif

Pertama, identifikasi beban kerja atau benchmark yang cukup mewakili aplikasi Anda. Langkah ini penting karena profil yang dikumpulkan dari beban kerja mengidentifikasi area yang panas dan dingin dalam kode. Saat menggunakan profil, compiler akan melakukan pengoptimalan agresif dan inline di area panas. Compiler juga dapat memilih untuk mengurangi ukuran kode area dingin saat menurunkan performa.

Mengidentifikasi beban kerja yang baik juga berguna untuk memantau performa secara umum.

Langkah 2: Mengumpulkan Profil

Pengumpulan profil melibatkan tiga langkah: - membuat kode native dengan instrumentasi, - menjalankan aplikasi berinstrumen di perangkat dan membuat profil, serta - menggabungkan/melanjutkan pemrosesan profil pada host.

Membuat Build Berinstrumen

Profil dikumpulkan dengan menjalankan beban kerja dari langkah 1 pada build aplikasi yang berinstrumen. Untuk membuat build berinstrumen, tambahkan -fprofile-generate ke tanda compiler dan linker. Tanda ini harus dikontrol oleh variabel build terpisah karena tanda ini tidak diperlukan selama build default.

Membuat Profil

Berikutnya, jalankan aplikasi berinstrumen pada perangkat dan buat profil. Profil dikumpulkan di memori saat biner berinstrumen dijalankan dan ditulis ke file saat keluar. Namun, fungsi yang terdaftar dengan atexit tidak akan dipanggil di aplikasi Android dan aplikasi akan dihentikan.

Aplikasi/beban kerja harus melakukan pekerjaan tambahan untuk menetapkan jalur bagi file profil, lalu memicu penulisan profil secara eksplisit.

  • Untuk menetapkan jalur file profil, panggil __llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw. %m berguna saat ada beberapa library bersama. %m` diperluas ke tanda tangan modul unik untuk library tersebut, yang menghasilkan profil terpisah per library. Lihat di sini untuk penentu pola berguna lainnya. PROFILE_DIR adalah direktori yang dapat ditulis dari aplikasi. Lihat demo untuk mendeteksi direktori ini saat runtime.
  • Untuk memicu penulisan profil secara eksplisit, panggil fungsi __llvm_profile_write_file.
extern "C" {
extern int __llvm_profile_set_filename(const char*);
extern int __llvm_profile_write_file(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_write_file();
  return;
}

Catatan: Membuat file profil akan lebih mudah jika beban kerja adalah biner mandiri, cukup setel variabel lingkungan LLVM_PROFILE_FILE ke %t/default-%m.profraw sebelum menjalankan biner.

Melanjutkan Pemrosesan Profil

File profil menggunakan format .profraw. File tersebut harus diambil terlebih dahulu dari perangkat menggunakan adb pull. Setelah pengambilan, gunakan utilitas llvm-profdata di NDK untuk mengonversi dari .profraw menjadi .profdata, yang kemudian dapat diteruskan ke compiler.

$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-profdata \
    merge --output=pgo_profile.profdata \
    <list-of-profraw-files>

Gunakan llvm-profdata dan clang dari rilis NDK yang sama untuk menghindari ketidakcocokan versi format file profil.

Langkah 3: Menggunakan Profil untuk Mem-build Aplikasi

Gunakan profil dari langkah sebelumnya selama merilis build aplikasi Anda dengan meneruskan -fprofile-use=<>.profdata ke compiler dan linker. Profil dapat digunakan bahkan saat kode berkembang — compiler Clang dapat menoleransi sedikit ketidakcocokan antara sumber dan profil.

Catatan: Secara umum, untuk sebagian besar library, profil bersifat umum di seluruh arsitektur. Misalnya, profil yang dihasilkan dari build arm64 library dapat digunakan untuk semua arsitektur. Masalahnya adalah jika ada jalur kode khusus arsitektur tertentu di library (arm vs x86 atau 32-bit vs 64-bit), profil terpisah harus digunakan untuk setiap konfigurasi tersebut.

Penutup

https://github.com/DanAlbert/ndk-samples/tree/pgo/pgo menunjukkan demo menyeluruh penggunaan PGO dari aplikasi. Demo ini memberikan detail tambahan yang dibahas sekilas dalam dokumen ini.

  • Aturan build CMake menunjukkan cara menyiapkan variabel CMake yang membuat kode native dengan instrumentasi. Saat variabel build tidak disetel, kode native akan dioptimalkan menggunakan profil PGO yang dibuat sebelumnya.
  • Pada build berinstrumen, pgodemo.cpp akan menulis profil sebagai eksekusi beban kerja.
  • Lokasi yang dapat ditulis untuk profil diperoleh saat runtime di MainActivity.kt menggunakan applicationContext.cacheDir.toString().
  • Untuk mengambil profil dari perangkat tanpa memerlukan adb root, gunakan urutan langkah adb di sini.