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 dibuat selama build dan input dari build tersebut. Contohnya adalah: file input, file output, target, dan file yang dikonfigurasi target.SkyKey
. Nama pendek yang tidak dapat diubah untuk mereferensikanSkyValue
, misalnya,FILECONTENTS:/tmp/foo
atauPACKAGE://foo
.SkyFunction
. Mem-build node berdasarkan kunci dan node dependennya.- Grafik node. Struktur data yang berisi hubungan dependensi antara node.
Skyframe
. Nama kode untuk framework evaluasi inkremental Bazel adalah yang menjadi dasar.
Evaluasi
Build dicapai dengan mengevaluasi node yang mewakili permintaan build.
Pertama, Bazel menemukan SkyFunction
yang sesuai dengan kunci dari level atas
SkyKey
. Fungsi ini kemudian meminta evaluasi
dari {i>node<i} yang diperlukan
mengevaluasi node tingkat atas, yang pada akhirnya menghasilkan panggilan SkyFunction
lainnya,
hingga node {i>leaf<i} tercapai. Simpul daun biasanya
adalah yang mewakili
file input dalam sistem file. Akhirnya, Bazel berakhir dengan nilai
SkyValue
tingkat teratas, beberapa efek samping (seperti file output dalam file
) dan grafik terarah asiklik dari dependensi antara node
yang terlibat dalam build.
SkyFunction
dapat meminta SkyKeys
dalam beberapa penerusan jika tidak dapat memberi tahu
memajukan semua {i>node<i} yang dibutuhkan
untuk melakukan tugasnya. Contoh sederhana adalah mengevaluasi
{i>node file<i} input yang ternyata adalah {i>symlink<i}: fungsi tersebut mencoba membaca
file, menyadari bahwa itu adalah {i>symlink<i}, dan dengan demikian mengambil node sistem file
yang mewakili target symlink. Tapi itu sendiri bisa
berupa {i>symlink<i}, di
dalam hal ini fungsi asli juga perlu mengambil targetnya.
Fungsi diwakili dalam kode oleh antarmuka SkyFunction
dan
layanan yang disediakan untuknya oleh
antarmuka yang disebut SkyFunction.Environment
. Ini
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
. Dalam kasus yang terakhir, node dependen dievaluasi, lalu builder node asli dipanggil lagi, namun kali ini panggilanenv.getValue
yang sama akan menampilkan bukannull
. - Minta evaluasi beberapa node lainnya dengan memanggil
env.getValues()
. Prinsip ini pada dasarnya sama, hanya saja node dependen adalah dievaluasi secara paralel. - Melakukan komputasi selama pemanggilan
- Memiliki efek samping, misalnya, menulis file ke sistem file. Kebutuhan perawatan diambil bahwa dua fungsi yang berbeda menghindari menginjak satu sama lain jari kaki. Secara umum, tulis efek samping ({i>data<i} mengalir keluar dari Bazel) tidak apa-apa, efek samping baca (di mana data mengalir ke dalam ke Bazel tanpa dependensi terdaftar) tidak, karena merupakan dependensi yang tidak terdaftar dan karenanya, dapat menyebabkan build inkremental yang salah.
Implementasi SkyFunction
yang berperilaku baik menghindari akses data dengan cara lain
daripada meminta dependensi (seperti dengan langsung membaca sistem file),
karena hal itu menyebabkan Bazel tidak mendaftarkan
dependensi data pada file tersebut
yang dibaca, sehingga menghasilkan build inkremental yang salah.
Setelah memiliki cukup data untuk melakukan tugasnya, fungsi harus menampilkan non-null
nilai yang menunjukkan penyelesaian.
Strategi evaluasi ini memiliki sejumlah manfaat:
- Hermetikitas. Jika fungsi hanya meminta data input dengan cara bergantung pada di node lainnya, Bazel dapat menjamin bahwa jika status inputnya sama, data yang sama ditampilkan. Jika semua fungsi langit bersifat determenistik, ini berarti bahwa seluruh build juga akan bersifat determenistik.
- Inkrementalitas yang benar dan sempurna. Jika semua data input dari semua fungsi direkam, Bazel hanya dapat membatalkan kumpulan {i>node<i} yang tepat yang perlu menjadi tidak valid saat data input berubah.
- Paralelisme. Karena fungsi hanya dapat berinteraksi satu sama lain melalui meminta dependensi, fungsi yang tidak bergantung satu sama lain dapat berjalan secara paralel dan Bazel dapat menjamin bahwa hasilnya sama seolah-olah tugas-tugas ini dijalankan secara berurutan.
Inkrementalitas
Karena fungsi hanya dapat mengakses data input berdasarkan {i>node<i} lain, Bazel dapat membuat grafik aliran data yang lengkap dari file {i>input<i} ke {i>output<i} file, dan menggunakan informasi ini hanya untuk membangun ulang {i>node<i} yang benar-benar membutuhkan yang akan dibuat ulang: penutupan transitif terbalik dari kumpulan file input yang diubah.
Secara khusus, ada dua kemungkinan strategi inkrementalitas: metode {i>bottom-up<i} dan dari atas ke bawah. Mana yang optimal tergantung pada bagaimana grafik dependensi itu.
Selama pembatalan validasi bottom-up, setelah grafik dibuat dan kumpulan perubahannya diketahui, semua node menjadi tidak valid dan bergantung pada mengubah file. Hal ini optimal jika node level teratas yang sama akan dibuat untuk mencoba lagi perintah. Perhatikan bahwa pembatalan validasi bottom-up perlu menjalankan
stat()
di semua file input dari build sebelumnya untuk menentukan apakah file tersebut diubah. Ini dapat ditingkatkan menggunakaninotify
atau mekanisme serupa untuk mempelajari mengubah file.Selama pembatalan validasi top-down, penutupan transitif node level atas diperiksa dan hanya {i>node<i} yang dipertahankan yang penutupan transitifnya bersih. Hal ini lebih baik jika grafik node berukuran besar, tetapi build berikutnya hanya memerlukan sebuah sebagian kecil saja: pembatalan validasi bottom-up akan membatalkan grafik yang lebih besar build pertama, tidak seperti pembatalan {i>top-down<i}, yang hanya menjelaskan grafik build kedua.
Bazel hanya melakukan pembatalan validasi bottom-up.
Untuk mendapatkan inkrementalitas lebih lanjut, Bazel menggunakan pemangkasan perubahan: jika node dibatalkan, namun setelah dibuat ulang, ditemukan bahwa nilai barunya sama sebagai nilai lamanya, node yang dibatalkan validasinya karena perubahan pada node ini "dibangkitkan".
Ini berguna, misalnya, jika seseorang mengubah komentar dalam file C++: maka
File .o
yang dihasilkan darinya akan sama, jadi tidak perlu memanggil
{i>link <i}lagi.
Penautan / Kompilasi Inkremental
Keterbatasan utama dari model ini adalah pembatalan validasi node masalah menyeluruh: saat dependensi berubah, node dependen akan selalu dibangun ulang dari awal, bahkan jika ada algoritma yang lebih baik yang akan memutasikan nilai lama {i>node<i} berdasarkan perubahannya. Beberapa contoh di mana berguna:
- Penautan inkremental
- Saat satu file class berubah dalam file JAR, dimungkinkan memodifikasi file JAR di tempat alih-alih membangunnya dari awal lagi.
Alasan mengapa Bazel tidak mendukung hal-hal ini dengan cara yang berprinsip ada dua hal:
- Peningkatan performa yang terjadi terbatas.
- Sulit untuk memvalidasi bahwa hasil mutasinya sama dengan hasil tersebut dari proses build ulang yang bersih, dan nilai-nilai Google untuk build yang bit-for-bit dapat diulang.
Sejauh ini, dimungkinkan untuk mencapai kinerja yang cukup baik dengan menguraikan dan mencapai evaluasi ulang parsial dengan cara tersebut. Misalnya, di aplikasi Android, Anda dapat membagi semua class menjadi beberapa grup dan dex mereka secara terpisah. Dengan cara ini, jika kelas dalam grup tidak berubah, dexing akan tidak harus diulangi.
Pemetaan ke konsep Bazel
Ini adalah ringkasan umum dari kunci SkyFunction
dan SkyValue
implementasi yang digunakan Bazel untuk menjalankan build:
- FileStateValue. Hasil
lstat()
. Untuk file yang ada, juga menghitung informasi tambahan untuk mendeteksi perubahan pada file tersebut. Ini adalah {i>node<i} tingkat terendah dalam grafik Skyframe dan tidak dependensi. - FileValue. Digunakan oleh apa pun yang peduli dengan konten aktual atau
penyelesaian jalur file. Bergantung pada
FileStateValue
yang sesuai dan symlink apa pun yang perlu diselesaikan (sepertiFileValue
untuka/b
memerlukan jalura
yang di-resolve dan jalura/b
yang di-resolve). Tujuan perbedaan antaraFileValue
danFileStateValue
penting karena yang disebut terakhir dapat digunakan jika isi file tidak benar-benar dibutuhkan. Misalnya, isi file tidak relevan saat mengevaluasi glob sistem file (sepertisrcs=glob(["*/*.java"])
). - DirectoryListingStateValue. Hasil
readdir()
. SukaFileStateValue
, ini adalah node tingkat terendah dan tidak memiliki dependensi. - DirectoryListingValue. Digunakan oleh apa pun yang
peduli tentang entri dari
sebuah direktori. Bergantung pada
DirectoryListingStateValue
yang sesuai, sebagai sertaFileValue
direktori yang terkait. - PackageValue. Mewakili versi file BUILD yang diurai. Bergantung pada
FileValue
dari fileBUILD
yang terkait, dan juga secara transitif padaDirectoryListingValue
yang digunakan untuk me-resolve glob dalam paket (struktur data yang mewakili isi fileBUILD
secara internal). - ConfiguredTargetValue. Mewakili target yang dikonfigurasi, yaitu tuple
serangkaian tindakan yang dihasilkan selama
analisis target dan
informasi yang diberikan ke target dependen yang dikonfigurasi. Bergantung pada
PackageValue
target yang sesuai ada,ConfiguredTargetValues
dependensi langsung, dan node khusus yang mewakili build konfigurasi Anda. - ArtifactValue. Merepresentasikan file dalam build, baik berupa sumber
artefak output. Artefak hampir sama dengan file, dan digunakan untuk
merujuk ke file selama eksekusi langkah-langkah build yang sebenarnya. File sumber
bergantung pada
FileValue
node yang terkait, dan artefak output bergantung padaActionExecutionValue
dari tindakan apa pun yang menghasilkan artefak. - ActionExecutionValue. Menyatakan eksekusi suatu tindakan. Bergantung pada
ArtifactValues
file inputnya. Tindakan yang dijalankan dimuat di dalam SkyKey-nya, yang bertentangan dengan konsep bahwa SkyKeys harus kecil. Perhatikan bahwaActionExecutionValue
danArtifactValue
tidak akan digunakan jika fase eksekusi tidak berjalan.
Sebagai alat bantu visual, diagram ini menunjukkan hubungan antara Implementasi SkyFunction setelah build Bazel itu sendiri: