Catat Tanggalnya: BazelCon 2023 akan diadakan pada 24-25 Oktober di Google Munich! Pelajari lebih lanjut

Bingkai langit-langit

Laporkan masalah Lihat sumber

Evaluasi paralel dan model inkrementalitas Bazel.

Model data

Model data terdiri dari item berikut:

  • SkyValue. Juga disebut dengan node. SkyValues adalah objek tetap yang berisi semua data yang dibuat selama proses build dan input build. Contohnya adalah: file input, file output, target, dan target yang dikonfigurasi.
  • SkyKey. Nama yang tidak dapat diubah untuk mereferensikan SkyValue, misalnya, FILECONTENTS:/tmp/foo atau PACKAGE://foo.
  • SkyFunction. Membuat node berdasarkan kunci dan node dependennya.
  • Grafik node. Struktur data yang berisi hubungan dependensi antara node.
  • Skyframe. Nama kode untuk framework evaluasi inkremental Bazel didasarkan.

Evaluasi

Build terdiri dari evaluasi node yang mewakili permintaan build (ini adalah status yang kami perjuangkan, tetapi ada banyak kode lama dalam prosesnya). Pertama, SkyFunction ditemukan dan dipanggil dengan kunci SkyKey level teratas. Fungsi ini kemudian meminta evaluasi node yang dibutuhkan untuk mengevaluasi node tingkat atas, yang pada akhirnya menghasilkan pemanggilan fungsi lainnya, dan seterusnya, sampai node daun tercapai (yang biasanya merupakan node yang mewakili file input dalam sistem file). Terakhir, kita berakhir dengan nilai SkyValue level atas, beberapa efek samping (seperti file output dalam sistem file) dan grafik asiklik terarah dari dependensi antara node yang terlibat dalam build.

SkyFunction dapat meminta SkyKeys dalam beberapa penerusan jika tidak dapat memberi tahu semua node yang diperlukan untuk melakukan tugasnya terlebih dahulu. Contoh sederhananya adalah mengevaluasi node file input yang ternyata merupakan symlink: fungsi tersebut mencoba membaca file, menyadari bahwa file tersebut adalah symlink, sehingga mengambil node sistem file yang mewakili target symlink. Tetapi elemen itu sendiri dapat berupa symlink, yang dalam hal ini fungsi asli juga perlu mengambil targetnya.

Fungsi direpresentasikan dalam kode oleh antarmuka SkyFunction dan layanan yang disediakan oleh antarmuka bernama SkyFunction.Environment. Berikut adalah hal-hal yang dapat dilakukan fungsi:

  • Minta evaluasi node lain dengan memanggil env.getValue. Jika node tersedia, nilainya akan ditampilkan, jika tidak, null akan ditampilkan dan fungsi itu sendiri diharapkan untuk menampilkan null. Pada kasus terakhir, node dependen akan dievaluasi, lalu builder node asli akan dipanggil lagi, tetapi kali ini panggilan env.getValue yang sama akan menampilkan nilai non-null.
  • Minta evaluasi beberapa node lainnya dengan memanggil env.getValues(). Ini pada dasarnya sama, kecuali bahwa node dependen dievaluasi secara paralel.
  • Melakukan komputasi selama pemanggilan
  • Memiliki efek samping, misalnya, menulis file ke sistem file. Perlu diperhatikan bahwa dua fungsi yang berbeda tidak saling mengikuti. Secara umum, tuliskan efek samping (saat data mengalir keluar dari Bazel) bukan merupakan hal yang baik, tetapi sebaiknya gunakan efek samping baca (saat data mengalir ke dalam ke Bazel tanpa dependensi yang terdaftar), karena keduanya merupakan dependensi yang tidak terdaftar dan dengan demikian, dapat menyebabkan build inkremental yang salah.

Implementasi SkyFunction tidak boleh mengakses data dengan cara selain meminta dependensi (seperti dengan langsung membaca sistem file), karena hal itu akan menyebabkan Bazel tidak mendaftarkan dependensi data pada file yang dibaca sehingga menghasilkan build inkremental yang salah.

Setelah fungsi memiliki cukup data untuk melakukan tugasnya, fungsi tersebut akan menampilkan nilai non-null yang menunjukkan penyelesaian.

Strategi evaluasi ini memiliki sejumlah manfaat:

  • Hermetik. Jika fungsi hanya meminta data input dengan cara bergantung pada node lain, Bazel dapat menjamin bahwa jika status input sama, data yang sama akan ditampilkan. Jika semua fungsi langit bersifat deterministik, artinya seluruh build juga akan bersifat deterministik.
  • Inkrementalitas yang benar dan sempurna. Jika semua data input dari semua fungsi dicatat, Bazel hanya dapat membatalkan validasi kumpulan node yang tepat yang perlu dibatalkan validasinya saat data input berubah.
  • Paralelisme. Karena fungsi hanya dapat berinteraksi satu sama lain dengan meminta dependensi, fungsi yang tidak bergantung satu sama lain dapat dijalankan secara paralel dan Bazel dapat menjamin bahwa hasilnya sama seperti jika dijalankan secara berurutan.

Incrementality

Karena fungsi hanya dapat mengakses data input dengan bergantung pada node lain, Bazel dapat membuat grafik aliran data lengkap dari file input ke file output, dan menggunakan informasi ini hanya untuk mem-build ulang node yang benar-benar perlu dibuat ulang: penutupan transitif terbalik dari kumpulan file input yang diubah.

Secara khusus, ada dua kemungkinan strategi inkrementalitas: strategi dari bawah ke atas dan strategi dari atas ke bawah. Mana yang optimal bergantung pada tampilan grafik dependensi.

  • Selama pembatalan validasi dari bawah ke atas, setelah grafik dibuat dan kumpulan input yang diubah diketahui, semua node menjadi tidak valid yang secara transitif bergantung pada file yang diubah. Hal ini optimal jika kita tahu bahwa node tingkat atas yang sama akan di-build lagi. Perhatikan bahwa pembatalan dari bawah ke atas memerlukan menjalankan stat() pada semua file input dari build sebelumnya untuk menentukan apakah file tersebut diubah. Anda dapat meningkatkannya menggunakan inotify atau mekanisme serupa untuk mempelajari file yang diubah.

  • Selama pembatalan validasi dari atas ke bawah, penutupan transitif node tingkat atas akan diperiksa dan hanya node tersebut yang dipertahankan yang penutupan transitifnya bersih. Hal ini akan lebih baik jika kita tahu bahwa grafik node saat ini besar, tetapi kita hanya membutuhkan subset kecil darinya pada build berikutnya: pembatalan validasi dari bawah ke atas akan membatalkan grafik yang lebih besar dari build pertama, tidak seperti pembatalan validasi dari atas ke bawah, yang hanya berjalan dengan grafik kecil dari build kedua.

Saat ini kami hanya melakukan pembatalan dari bawah ke atas.

Untuk mendapatkan inkrementalitas lebih lanjut, kita akan menggunakan pemangkasan perubahan: jika node menjadi tidak valid, tetapi setelah di-build ulang, ternyata nilai barunya sama dengan nilai lamanya, node yang dibatalkan validasinya karena perubahan dalam node ini “dibangkitkan kembali”.

Hal ini berguna, misalnya, jika seseorang mengubah komentar dalam file C++: lalu file .o yang dihasilkannya akan sama, sehingga kita tidak perlu memanggil penaut lagi.

Penautan / Kompilasi Inkremental

Batasan utama dari model ini adalah bahwa pembatalan node adalah masalah semua atau tidak sama sekali: ketika dependensi berubah, node dependen selalu dibuat ulang dari awal, meskipun algoritme yang lebih baik akan ada yang dapat mengubah nilai node lama berdasarkan perubahan tersebut. Berikut ini beberapa contoh yang berguna:

  • Penautan inkremental
  • Saat satu file .class berubah di .jar, secara teoretis kita dapat mengubah file .jar, bukan membuatnya lagi dari awal.

Alasan Bazel saat ini tidak mendukung hal-hal ini dengan cara yang prinsipnya (kami memiliki beberapa dukungan untuk penautan inkremental, tetapi tidak diimplementasikan dalam Skyframe) adalah dua kali lipat: kami hanya memiliki peningkatan performa terbatas dan sulit untuk menjamin bahwa hasil mutasinya sama dengan build ulang yang bersih, dan build Google value yang bit-bit-bit-dapat diulang.

Hingga saat ini, kita selalu dapat mencapai performa yang cukup baik hanya dengan menguraikan langkah build yang mahal dan mencapai evaluasi ulang sebagian dengan cara tersebut: tindakan ini membagi semua class dalam aplikasi menjadi beberapa grup dan melakukan dexing pada keduanya secara terpisah. Dengan demikian, jika class dalam suatu grup tidak berubah, dexing tidak perlu diulang.

Pemetaan ke konsep Bazel

Ini adalah ringkasan kasar dari beberapa implementasi SkyFunction yang digunakan Bazel untuk melakukan build:

  • FileStateValue. Hasil lstat(). Untuk file yang sudah ada, kami juga menghitung informasi tambahan untuk mendeteksi perubahan pada file. Ini adalah node level terendah dalam grafik Skyframe dan tidak memiliki dependensi.
  • FileValue. Digunakan oleh apa pun yang peduli dengan konten yang sebenarnya dan/atau menyelesaikan jalur file. Bergantung pada FileStateValue yang sesuai dan symlink apa pun yang perlu diselesaikan (seperti FileValue untuk a/b memerlukan jalur a yang telah di-resolve dan jalur a/b yang telah diselesaikan). Perbedaan antara FileStateValue penting karena dalam beberapa kasus (misalnya, mengevaluasi glob sistem file (seperti srcs=glob(["*/*.java"])) konten file sebenarnya tidak diperlukan.
  • DirectoryListingValue. Pada dasarnya, hasil dari readdir(). Bergantung pada FileValue terkait yang terkait dengan direktori.
  • PackageValue yang diinginkan. Menyatakan versi terurai dari file BUILD. Bergantung pada FileValue file BUILD terkait, dan juga secara transitif pada DirectoryListingValue apa pun yang digunakan untuk me-resolve glob dalam paket (struktur data yang mewakili isi file BUILD secara internal)
  • ConfiguredTargetValue. Merepresentasikan target yang dikonfigurasi, yang merupakan tuple dari kumpulan tindakan yang dihasilkan selama analisis target dan informasi yang diberikan ke target yang dikonfigurasi yang bergantung pada target ini. Bergantung pada PackageValue target yang sesuai, ConfiguredTargetValues dependensi langsung, dan node khusus yang mewakili konfigurasi build.
  • ArtifactValue yang diinginkan. Menyatakan file dalam build, baik berupa sumber maupun artefak output (artefak hampir setara dengan file, dan digunakan untuk merujuk ke file selama eksekusi langkah build yang sebenarnya). Untuk file sumber, dependensinya bergantung pada FileValue node terkait, untuk artefak output, dependensinya bergantung pada ActionExecutionValue dari tindakan apa pun yang menghasilkan artefak.
  • ActionExecutionValue. Merepresentasikan eksekusi tindakan. Bergantung pada ArtifactValues file inputnya. Tindakan yang dijalankan saat ini berada dalam kunci sky-nya, yang bertentangan dengan konsep bahwa kunci sky harus berukuran kecil. Kami sedang berupaya menyelesaikan perbedaan ini (perhatikan bahwa ActionExecutionValue dan ArtifactValue tidak digunakan jika kita tidak menjalankan fase eksekusi di Skyframe).