Model evaluasi dan inkrementalitas paralel Bazel.
Model data
Model data terdiri dari item berikut:
SkyValue
. Juga disebut node.SkyValues
adalah objek yang tidak dapat diubah dan berisi semua data yang dibangun selama build dan input build. Contohnya adalah: file input, file output, target, dan target yang dikonfigurasi.SkyKey
. Nama pendek yang tidak dapat diubah untuk mereferensikanSkyValue
, misalnya,FILECONTENTS:/tmp/foo
atauPACKAGE://foo
.SkyFunction
. Membangun node berdasarkan kuncinya dan node dependen.- Grafik node. Struktur data yang berisi hubungan dependensi antar-node.
Skyframe
. Nama kode untuk framework evaluasi inkremental yang didasarkan oleh Bazel.
Evaluasi
Build dicapai dengan mengevaluasi node yang mewakili permintaan build.
Pertama, Bazel akan menemukan SkyFunction
yang sesuai dengan kunci SkyKey
level atas. Fungsi ini kemudian meminta evaluasi node yang diperlukan untuk
mengevaluasi node level atas, yang kemudian menghasilkan panggilan SkyFunction
lain,
hingga node daun tercapai. {i>Leaf node<i} biasanya adalah {i>node<i} yang
mewakili file input dalam sistem file. Terakhir, Bazel 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 mengetahui
lebih awal semua node yang diperlukan untuk melakukan tugasnya. Contoh sederhananya adalah mengevaluasi node file input yang ternyata adalah symlink: fungsi tersebut mencoba membaca file, menyadari bahwa file tersebut adalah symlink, dan dengan demikian mengambil node sistem file yang merepresentasikan target symlink. Namun, fungsi tersebut bisa berupa symlink, sehingga fungsi asli juga perlu mengambil targetnya.
Fungsi direpresentasikan dalam kode oleh antarmuka SkyFunction
dan
layanan yang diberikan kepadanya oleh antarmuka yang disebut SkyFunction.Environment
. Berikut adalah beberapa 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 menampilkannull
. Dalam kasus yang kedua, node dependen dievaluasi, lalu builder node asli dipanggil lagi, tetapi kali ini panggilanenv.getValue
yang sama akan menampilkan nilai non-null
. - Minta evaluasi beberapa node lain dengan memanggil
env.getValues()
. Hal ini pada dasarnya sama, kecuali bahwa node dependen dievaluasi secara paralel. - Melakukan komputasi selama pemanggilan mereka
- Memiliki efek samping, misalnya, menulis file ke sistem file. Harus diperhatikan bahwa dua fungsi yang berbeda tidak akan saling menginjak jari kaki. Secara umum, menulis efek samping (saat data mengalir keluar dari Bazel) tidak masalah, sedangkan efek samping baca (saat data mengalir ke dalam Bazel tanpa dependensi terdaftar) tidak akan terjadi, karena merupakan dependensi yang tidak terdaftar, sehingga dapat menyebabkan build inkremental yang salah.
Implementasi SkyFunction
yang berperilaku baik akan menghindari akses data dengan cara lain
selain meminta dependensi (seperti dengan langsung membaca sistem file),
karena hal tersebut menyebabkan Bazel tidak mendaftarkan dependensi data pada file
yang dibaca, sehingga menghasilkan build inkremental yang salah.
Setelah memiliki cukup data untuk melakukan tugasnya, fungsi harus menampilkan nilai non-null
yang menunjukkan penyelesaian.
Strategi evaluasi ini memiliki sejumlah manfaat:
- Hermetisitas. Jika fungsi hanya meminta data input dengan cara bergantung pada node lainnya, Bazel dapat menjamin bahwa jika status input sama, data yang sama akan ditampilkan. Jika semua fungsi langit bersifat deterministik, seluruh build juga akan bersifat deterministik.
- Inkrementalitas yang benar dan sempurna. Jika semua data input dari semua fungsi direkam, Bazel hanya dapat membatalkan validasi kumpulan node tepat yang perlu dibatalkan saat data input berubah.
- Paralelisme. Karena fungsi hanya dapat berinteraksi satu sama lain dengan cara meminta dependensi, fungsi yang tidak bergantung satu sama lain dapat dijalankan secara paralel dan Bazel dapat menjamin bahwa hasilnya akan sama seolah-olah dijalankan secara berurutan.
Inkrementalitas
Karena fungsi hanya dapat mengakses data input bergantung pada node lain, Bazel dapat membuat grafik aliran data lengkap dari file input ke file output, dan menggunakan informasi ini hanya untuk membangun ulang node yang benar-benar perlu di-build ulang: penutupan transitif terbalik dari kumpulan file input yang diubah.
Secara khusus, ada dua kemungkinan strategi inkrementalitas: yang dari bawah ke atas dan yang top-down. Mana yang optimal bergantung pada tampilan grafik dependensi.
Selama pembatalan bottom-up, setelah grafik dibuat dan kumpulan input yang diubah diketahui, semua node menjadi tidak valid dan secara transitif bergantung pada file yang diubah. Hal ini optimal jika node tingkat atas yang sama akan dibangun lagi. Perhatikan bahwa pembatalan validasi bottom-up mengharuskan Anda menjalankan
stat()
pada semua file input build sebelumnya untuk menentukan apakah input diubah. Hal ini dapat ditingkatkan dengan menggunakaninotify
atau mekanisme serupa untuk mempelajari file yang diubah.Selama pembatalan validasi dari atas ke bawah, penutupan transitif node level atas akan diperiksa dan hanya node tersebut yang dipertahankan, yang penutupan transitifnya bersih. Lebih baik jika grafik node berukuran besar, tetapi build berikutnya hanya memerlukan sebagian kecilnya: pembatalan bottom-up akan membatalkan grafik yang lebih besar pada build pertama, tidak seperti pembatalan top-down, yang hanya menjalankan grafik kecil dari build kedua.
Bazel hanya melakukan pembatalan dari bawah ke atas.
Untuk mendapatkan inkrementalitas lebih lanjut, Bazel menggunakan pemangkasan perubahan: jika node dibatalkan, tetapi setelah dibuat ulang, ditemukan bahwa nilai barunya sama dengan nilai lamanya, node yang dibatalkan validasinya karena perubahan dalam node ini akan "dihidupkan kembali".
Hal ini berguna, misalnya, jika seseorang mengubah komentar dalam file C++: file .o
yang dihasilkan dari file tersebut akan sama, sehingga Anda tidak perlu memanggil linker lagi.
Penautan / Kompilasi Inkremental
Keterbatasan utama model ini adalah pembatalan validasi node adalah hal yang sama sekali tidak penting: saat dependensi berubah, node dependen selalu dibuat ulang dari awal, meskipun akan ada algoritma yang lebih baik yang akan mengubah nilai lama node berdasarkan perubahan tersebut. Beberapa contoh yang akan berguna:
- Penautan inkremental
- Saat satu file class berubah dalam file JAR, Anda dapat mengubah file JAR di tempat, dan bukan mem-build-nya dari awal lagi.
Alasan Bazel tidak mendukung hal-hal ini secara prinsip adalah dua hal:
- Peningkatan performa terbatas.
- Kesulitan untuk memvalidasi bahwa hasil mutasi sama dengan proses build ulang bersih, dan build nilai Google yang dapat diulang dengan bit-for-bit.
Hingga saat ini, performa yang cukup baik dapat dicapai dengan mengurai langkah build yang mahal dan mencapai evaluasi ulang sebagian dengan cara tersebut. Misalnya, di aplikasi Android, Anda dapat membagi semua class menjadi beberapa grup dan men-dex-nya secara terpisah. Dengan demikian, jika class dalam grup tidak berubah, dexing tidak harus diulangi.
Pemetaan ke konsep Bazel
Ini adalah ringkasan umum dari implementasi utama SkyFunction
dan SkyValue
yang digunakan Bazel untuk menjalankan build:
- FileStateValue. Hasil
lstat()
. Untuk file yang ada, fungsi ini juga menghitung informasi tambahan untuk mendeteksi perubahan pada file tersebut. Ini adalah node level terendah dalam grafik Skyframe dan tidak memiliki dependensi. - FileValue. Digunakan oleh apa pun yang peduli dengan
isi aktual atau jalur file yang telah diselesaikan. Bergantung pada
FileStateValue
yang sesuai dan symlink apa pun yang perlu di-resolve (sepertiFileValue
untuka/b
memerlukan jalura
yang di-resolve dan jalura/b
yang di-resolve). Perbedaan antaraFileValue
danFileStateValue
adalah hal penting karena symlink yang dapat digunakan jika konten file tidak benar-benar dibutuhkan. Misalnya, konten file tidak relevan saat mengevaluasi glob sistem file (sepertisrcs=glob(["*/*.java"])
). - DirectoryListingStateValue. Hasil
readdir()
. SepertiFileStateValue
, ini adalah node level terendah dan tidak memiliki dependensi. - DirectoryListingValue. Digunakan oleh apa pun yang peduli dengan entri
direktori. Bergantung pada
DirectoryListingStateValue
yang sesuai, sertaFileValue
terkait dari direktori. - PackageValue. Menampilkan versi file BUILD yang diuraikan. Bergantung pada
FileValue
fileBUILD
terkait, dan juga secara transitif padaDirectoryListingValue
yang digunakan untuk me-resolve glob dalam paket (struktur data yang mewakili konten fileBUILD
secara internal). - ConfiguredTargetValue. Merepresentasikan target yang dikonfigurasi, yang merupakan tuple
dari serangkaian tindakan yang dihasilkan selama analisis target dan
informasi yang diberikan ke target dependen yang dikonfigurasi. Bergantung pada
PackageValue
tempat target yang sesuai berada,ConfiguredTargetValues
dependensi langsung, dan node khusus yang mewakili konfigurasi build. - ArtifactValue akan ditambahkan. Merepresentasikan file dalam build, baik berupa sumber maupun
artefak output. Artefak hampir setara dengan file, dan digunakan untuk
merujuk pada file selama eksekusi langkah-langkah build yang sebenarnya. File sumber
bergantung pada
FileValue
node terkait, dan artefak output bergantung padaActionExecutionValue
tindakan apa pun yang menghasilkan artefak. - ActionExecutionValue Menggambarkan eksekusi suatu tindakan. Bergantung pada
ArtifactValues
file inputnya. Tindakan yang dijalankan termuat dalam SkyKey-nya, yang bertentangan dengan konsep bahwa SkyKey harus berukuran kecil. Perhatikan bahwaActionExecutionValue
danArtifactValue
tidak digunakan jika fase eksekusi tidak berjalan.
Sebagai bantuan visual, diagram ini menunjukkan hubungan antara implementasi SkyFunction setelah mem-build Bazel itu sendiri: