Evaluasi paralel dan model inkrementalitas Bazel.
Model data
Model data terdiri dari item berikut:
SkyValue
. Juga disebut node.SkyValues
adalah objek yang tidak dapat diubah 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 pendek yang tidak dapat diubah untuk mereferensikanSkyValue
, misalnya,FILECONTENTS:/tmp/foo
atauPACKAGE://foo
.SkyFunction
. Membuat node berdasarkan kunci dan node dependennya.- Grafik node. Struktur data yang berisi hubungan dependensi antar node.
Skyframe
. Nama kode untuk framework evaluasi inkremental yang menjadi dasar Bazel.
Evaluasi
Build dicapai dengan mengevaluasi node yang mewakili permintaan build.
Pertama, Bazel menemukan SkyFunction
yang sesuai dengan kunci SkyKey
level teratas. Fungsi tersebut kemudian meminta evaluasi node yang diperlukan untuk
mengevaluasi node tingkat atas, yang pada akhirnya menghasilkan panggilan SkyFunction
lainnya,
hingga node daun tercapai. Node daun biasanya mewakili
file input dalam sistem file. Terakhir, Bazel akan berakhir dengan nilai
SkyValue
tingkat teratas, beberapa efek samping (seperti file output dalam sistem
file), dan grafik acyclic terarah dari dependensi antar-node
yang terlibat dalam build.
SkyFunction
dapat meminta SkyKeys
dalam beberapa tahap jika tidak dapat mengetahui
secara pasti semua node yang diperlukan untuk melakukan tugasnya. Contoh sederhananya adalah mengevaluasi node file input yang ternyata merupakan symlink: fungsi mencoba membaca file, menyadari bahwa file tersebut adalah symlink, sehingga mengambil node sistem file yang mewakili target symlink. Namun, itu sendiri dapat berupa symlink, yang
dalam hal ini fungsi asli juga harus mengambil targetnya.
Fungsi direpresentasikan dalam kode oleh antarmuka SkyFunction
dan
layanan yang disediakan untuknya oleh antarmuka yang disebut SkyFunction.Environment
. Berikut ini
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 menampilkannull
. Dalam kasus terakhir, 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 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 mengganggu. Secara umum, efek samping tulis (saat data mengalir keluar dari Bazel) tidak masalah, efek samping baca (saat data mengalir ke dalam Bazel tanpa dependensi terdaftar) tidak boleh, karena merupakan dependensi yang tidak terdaftar dan dengan demikian, dapat menyebabkan build inkremental yang salah.
Implementasi SkyFunction
yang berperilaku baik menghindari akses data dengan cara lain
selain meminta dependensi (seperti dengan membaca sistem file secara langsung),
karena hal itu menyebabkan Bazel tidak mendaftarkan dependensi data pada file
yang dibaca, sehingga menghasilkan build inkremental yang salah.
Setelah memiliki cukup data untuk melakukan tugasnya, fungsi akan menampilkan nilai non-null
yang menunjukkan penyelesaian.
Strategi evaluasi ini memiliki sejumlah manfaat:
- Hermetisitas. Jika fungsi hanya meminta data input dengan bergantung pada node lain, Bazel dapat menjamin bahwa jika status input sama, data yang sama akan ditampilkan. Jika semua fungsi sky bersifat deterministik, ini berarti seluruh build juga akan bersifat deterministik.
- Koreksi dan sempurnakan inkrementalitas. Jika semua data input dari semua fungsi direkam, 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 cara meminta dependensi, fungsi yang tidak bergantung satu sama lain dapat berjalan secara paralel dan Bazel dapat menjamin bahwa hasilnya sama seperti jika dijalankan secara berurutan.
Inkrementalitas
Karena fungsi hanya dapat mengakses data input dengan bergantung pada node lain, Bazel dapat membuat grafik alur data lengkap dari file input ke file output, dan menggunakan informasi ini untuk hanya mem-build 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: strategi bottom-up dan top-down. Mana yang optimal bergantung pada tampilan grafik dependensi.
Selama pembatalan validasi bottom-up, setelah grafik dibuat dan kumpulan input yang berubah diketahui, semua node akan dibatalkan validasinya yang secara transitif bergantung pada file yang diubah. Hal ini optimal jika node tingkat teratas yang sama akan dibuat lagi. Perhatikan bahwa pembatalan validasi dari bawah ke atas memerlukan pengoperasian
stat()
pada semua file input dari build sebelumnya untuk menentukan apakah file tersebut 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 tingkat atas akan diperiksa dan hanya node yang penutupan transitifnya bersih yang akan disimpan. Hal ini lebih baik jika grafik node besar, tetapi build berikutnya hanya memerlukan subkumpulan kecil: pembatalan validasi bottom-up akan membatalkan validasi grafik yang lebih besar dari build pertama, tidak seperti pembatalan validasi top-down, yang hanya menelusuri grafik kecil dari build kedua.
Bazel hanya melakukan pembatalan validasi dari bawah ke atas.
Untuk mendapatkan inkrementalitas lebih lanjut, Bazel menggunakan pemangkasan perubahan: jika node dibuat tidak valid, tetapi setelah di-build ulang, ditemukan bahwa nilai barunya sama dengan nilai lama, node yang dibuat tidak valid karena perubahan pada node ini akan "dibangkitkan".
Hal ini berguna, misalnya, jika seseorang mengubah komentar dalam file C++: file
.o
yang dihasilkan darinya akan sama, sehingga tidak perlu memanggil
penaut lagi.
Penautan / Kompilasi Inkremental
Batasan utama model ini adalah bahwa pembatalan validasi node adalah hal yang mutlak: saat dependensi berubah, node dependen selalu dibangun ulang dari awal, meskipun akan ada algoritma yang lebih baik yang akan memutasi nilai lama node berdasarkan perubahan. Beberapa contoh saat hal ini berguna:
- Penautan inkremental
- Jika satu file class berubah dalam file JAR, Anda dapat mengubah file JAR di tempat, bukan mem-build-nya dari awal lagi.
Alasan Bazel tidak mendukung hal-hal ini dengan cara yang mendasar ada dua:
- Peningkatan performanya terbatas.
- Kesulitan untuk memvalidasi bahwa hasil mutasi sama dengan build yang di-build ulang, dan Google menghargai build yang dapat diulang bit demi bit.
Hingga saat ini, Anda dapat mencapai performa yang cukup baik dengan menguraikan 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 class tersebut secara terpisah. Dengan cara ini, jika class dalam grup tidak berubah, proses dexing tidak perlu dilakukan ulang.
Pemetaan ke konsep Bazel
Berikut adalah ringkasan tingkat tinggi dari implementasi SkyFunction
dan SkyValue
utama yang digunakan Bazel untuk melakukan build:
- FileStateValue. Hasil
lstat()
. Untuk file yang ada, fungsi ini juga menghitung informasi tambahan untuk mendeteksi perubahan pada file. Ini adalah node tingkat terendah dalam grafik Skyframe dan tidak memiliki dependensi. - FileValue. Digunakan oleh apa pun yang memperhatikan konten sebenarnya atau
jalur file yang di-resolve. 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
penting karena yang terakhir dapat digunakan jika konten file tidak benar-benar diperlukan. Misalnya, konten file tidak relevan saat mengevaluasi glob sistem file (sepertisrcs=glob(["*/*.java"])
). - DirectoryListingStateValue. Hasil
readdir()
. SepertiFileStateValue
, ini adalah node tingkat terendah dan tidak memiliki dependensi. - DirectoryListingValue. Digunakan oleh apa pun yang memperhatikan entri
direktori. Bergantung pada
DirectoryListingStateValue
yang sesuai, sertaFileValue
direktori terkait. - PackageValue. Mewakili versi file BUILD yang diuraikan. Bergantung pada
FileValue
fileBUILD
terkait, dan juga secara transitif padaDirectoryListingValue
apa pun 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 kumpulan tindakan yang dihasilkan selama analisis target dan
informasi yang diberikan ke target yang dikonfigurasi dependen. Bergantung pada
PackageValue
tempat target yang sesuai berada,ConfiguredTargetValues
dependensi langsung, dan node khusus yang mewakili konfigurasi build. - ArtifactValue. Merepresentasikan file dalam build, baik sumber maupun
artefak output. Artefak hampir setara dengan file, dan digunakan untuk
merujuk ke file selama eksekusi langkah build yang sebenarnya. File sumber
bergantung pada
FileValue
node terkait, dan artefak output bergantung padaActionExecutionValue
tindakan apa pun yang menghasilkan artefak. - ActionExecutionValue. Merepresentasikan eksekusi tindakan. Bergantung pada
ArtifactValues
file inputnya. Tindakan yang dijalankannya terdapat dalam SkyKey-nya, yang bertentangan dengan konsep bahwa SkyKey harus kecil. Perhatikan bahwaActionExecutionValue
danArtifactValue
tidak digunakan jika fase eksekusi tidak berjalan.
Sebagai bantuan visual, diagram ini menunjukkan hubungan antara implementasi SkyFunction setelah build Bazel itu sendiri: