Evaluasi paralel dan model inkrementalitas Bazel.
Model data
Model data terdiri dari item berikut:
SkyValue
. Juga disebut dengan node.SkyValues
adalah objek tidak dapat diubah yang 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 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 atas. Fungsi ini kemudian meminta evaluasi node yang diperlukan untuk
mengevaluasi node tingkat atas, yang kemudian akan menghasilkan panggilan SkyFunction
lain,
hingga node daun tercapai. Node daun biasanya merupakan node
yang mewakili file input di sistem file. Terakhir, Bazel menghasilkan nilai
SkyValue
tingkat 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
di awal semua node yang diperlukan untuk melakukan tugasnya. Contoh sederhana adalah mengevaluasi
node file input yang ternyata adalah symlink: fungsi tersebut mencoba membaca
file, menyadari bahwa itu adalah symlink, dan dengan demikian mengambil node sistem file
yang mewakili target symlink. Namun, kode itu sendiri dapat berupa symlink, sehingga fungsi asal juga harus mengambil targetnya.
Fungsi ditunjukkan dalam kode oleh antarmuka SkyFunction
dan
layanan yang disediakan oleh antarmuka yang disebut 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 menampilkannull
. Pada kasus yang terakhir, node dependen akan dievaluasi, lalu builder node asli dipanggil lagi, tetapi kali ini panggilanenv.getValue
yang sama akan menampilkan nilai non-null
. - Minta evaluasi beberapa node lainnya dengan memanggil
env.getValues()
. Proses ini pada dasarnya sama, hanya saja node dependen dievaluasi secara paralel. - Melakukan komputasi selama pemanggilan
- Memiliki efek samping, misalnya, menulis file ke sistem file. Harus diperhatikan bahwa dua fungsi yang berbeda menghindari menginjak kaki satu sama lain. Secara umum, efek samping tulis (ketika data mengalir keluar dari Bazel) tidak masalah, efek samping baca (saat data mengalir ke dalam Bazel tanpa dependensi terdaftar) tidak diperbolehkan, karena merupakan dependensi yang tidak terdaftar, sehingga dapat menyebabkan build inkremental yang salah.
Implementasi SkyFunction
yang berperilaku baik menghindari akses data dengan cara apa pun selain meminta dependensi (misalnya 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 akan menampilkan nilai non-null
yang menunjukkan penyelesaian.
Strategi evaluasi ini memiliki sejumlah manfaat:
- Hermetikitas. 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 determenistik, artinya seluruh build juga akan bersifat determenistik.
- Inkrementalitas yang benar dan sempurna. Jika semua data input untuk semua fungsi dicatat, Bazel hanya dapat membatalkan kumpulan node yang tepat yang perlu divalidasi 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 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 untuk hanya membangun ulang node yang benar-benar perlu dibangun ulang: penutupan transitif terbalik dari kumpulan file input yang diubah.
Secara khusus, ada dua kemungkinan strategi inkrementalitas: strategi bottom-up dan top-down. Dependensi mana yang optimal tergantung pada tampilan grafik dependensi.
Selama pembatalan validasi bottom-up, setelah grafik dibuat dan kumpulan input yang diubah diketahui, semua node akan menjadi tidak valid sehingga bergantung secara transitif pada file yang diubah. Cara ini optimal jika node tingkat atas yang sama akan dibuat kembali. Perhatikan bahwa pembatalan validasi bottom-up mengharuskan Anda menjalankan
stat()
pada semua file input build sebelumnya untuk mengetahui apakah file tersebut berubah. Hal ini dapat ditingkatkan menggunakaninotify
atau mekanisme serupa untuk mempelajari file yang diubah.Selama pembatalan validasi top-down, penutupan transitif node level teratas diperiksa dan hanya node yang penutupan transitifnya bersih yang dipertahankan. Hal ini lebih baik jika grafik node berukuran besar, tetapi build berikutnya hanya memerlukan subset kecil: pembatalan validasi bottom-up akan membatalkan grafik yang lebih besar dari build pertama, tidak seperti pembatalan validasi top-down, yang hanya menjalankan grafik kecil build kedua.
Bazel hanya melakukan pembatalan validasi bottom-up.
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".
Ini berguna, misalnya, jika seseorang mengubah komentar dalam file C++: maka file .o
yang dihasilkan darinya akan sama sehingga tidak perlu memanggil linker lagi.
Penautan / Kompilasi Inkremental
Keterbatasan utama model ini adalah pembatalan validasi node tidak terjadi apa-apa: saat dependensi berubah, node dependen akan selalu dibuat ulang dari awal, meskipun ada algoritma yang lebih baik yang akan mengubah nilai lama node berdasarkan perubahan tersebut. Beberapa contoh yang dapat membantu hal ini:
- Penautan inkremental
- Saat satu file class berubah dalam file JAR, Anda dapat mengubah file JAR di tempat, bukan membuatnya dari awal lagi.
Alasan Bazel tidak mendukung hal-hal ini dengan cara berprinsip memiliki dua alasan:
- Peningkatan performa yang terjadi terbatas.
- Kesulitan untuk memvalidasi bahwa hasil mutasi sama dengan pembuatan ulang yang bersih, dan nilai Google build yang dapat diulangi bit-for-bit.
Hingga saat ini, Anda dapat mencapai performa yang cukup baik dengan menguraikan langkah build yang mahal dan mencapai evaluasi ulang parsial dengan cara tersebut. Misalnya, di aplikasi Android, Anda dapat membagi semua class menjadi beberapa grup dan melakukan dex secara terpisah. Dengan cara ini, jika class dalam grup tidak berubah, dexing tidak harus diulang.
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 tersebut 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 konten aktual atau
jalur file yang telah di-resolve. Bergantung pada
FileStateValue
yang sesuai dan symlink apa pun yang perlu di-resolve (sepertiFileValue
untuka/b
memerlukan jalur yang di-resolvea
dan jalura/b
yang di-resolve). Perbedaan antaraFileValue
danFileStateValue
penting karena yang terakhir ini dapat digunakan jika isi file benar-benar tidak diperlukan. Misalnya, konten file tidak relevan saat mengevaluasi glob sistem file (sepertisrcs=glob(["*/*.java"])
). - DirectoryListingStateValue. Hasil
readdir()
. SepertiFileStateValue
, node ini adalah node level 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 diurai. 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. Mewakili target yang dikonfigurasi, yang merupakan tuple
dari kumpulan tindakan yang dihasilkan selama analisis target dan
informasi yang diberikan ke target dependen yang dikonfigurasi. Bergantung pada
PackageValue
target yang sesuai berada,ConfiguredTargetValues
dependensi langsung, dan node khusus yang mewakili konfigurasi build. - ArtifactValue. Merepresentasikan file dalam build, baik berupa artefak
output maupun sumber. Artefak hampir sama dengan file, dan digunakan untuk
merujuk ke file selama eksekusi langkah build sebenarnya. File sumber
bergantung pada
FileValue
node terkait, dan artefak output bergantung padaActionExecutionValue
dari tindakan apa pun yang menghasilkan artefak tersebut. - ActionExecutionValue. Menyatakan eksekusi suatu tindakan. Bergantung pada
ArtifactValues
file inputnya. Tindakan yang dijalankan dimuat dalam SkyKey-nya, yang bertentangan dengan konsep bahwa SkyKeys harus berukuran kecil. Perhatikan bahwaActionExecutionValue
danArtifactValue
tidak digunakan jika fase eksekusi tidak berjalan.
Sebagai bantuan visual, diagram ini menunjukkan hubungan antara implementasi SkyFunction setelah pembuatan Bazel itu sendiri: