Bingkai

Laporkan masalah Lihat sumber Nightly · 8.0 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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 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 antar node.
  • Skyframe. Nama kode untuk framework evaluasi inkremental yang menjadi dasar Bazel.

Evaluasi

Build terdiri dari mengevaluasi node yang mewakili permintaan build (ini adalah status yang kita perjuangkan, tetapi ada banyak kode lama yang menghalangi). Pertama, SkyFunction-nya ditemukan dan dipanggil dengan kunci SkyKey level atas. Fungsi tersebut kemudian meminta evaluasi node yang diperlukan untuk mengevaluasi node tingkat atas, yang pada akhirnya menghasilkan pemanggilan fungsi lain, dan seterusnya, hingga node daun tercapai (yang biasanya merupakan node yang mewakili file input dalam sistem file). Terakhir, kita akan mendapatkan 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 kali penerusan jika tidak dapat mengetahui terlebih dahulu 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, dan dengan demikian mengambil node sistem file yang mewakili target symlink. Namun, itu sendiri dapat berupa symlink, 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 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 menampilkan null. Dalam kasus terakhir, node dependen dievaluasi, lalu builder node asli dipanggil lagi, tetapi kali ini panggilan env.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. Anda harus berhati-hati agar 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 tidak boleh mengakses data dengan cara lain selain meminta dependensi (seperti dengan membaca sistem file secara langsung), karena hal itu akan 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, artinya seluruh build juga akan bersifat deterministik.
  • Koreksi dan sempurnakan inkrementalitas. 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.

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 diubah diketahui, semua node akan dibatalkan validasinya yang secara transitif bergantung pada file yang diubah. Hal ini optimal jika kita tahu bahwa 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 telah diubah. Hal ini dapat ditingkatkan dengan menggunakan inotify atau mekanisme serupa untuk mempelajari file yang diubah.

  • Selama pembatalan validasi dari atas ke bawah, penutupan transitif node tingkat atas diperiksa dan hanya node yang penutupan transitifnya bersih yang dipertahankan. Hal ini lebih baik jika kita tahu bahwa grafik node saat ini berukuran besar, tetapi kita hanya memerlukan sebagian kecilnya dalam build berikutnya: pembatalan validasi dari bawah ke atas akan membatalkan validasi grafik yang lebih besar dari build pertama, tidak seperti pembatalan validasi dari atas ke bawah, yang hanya menelusuri grafik kecil dari build kedua.

Saat ini kami hanya melakukan pembatalan validasi dari bawah ke atas.

Untuk mendapatkan inkrementalitas lebih lanjut, kita menggunakan pemangkasan perubahan: jika node tidak valid, tetapi setelah di-build ulang, ditemukan bahwa nilai barunya sama dengan nilai lama, node yang 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 kita tidak perlu memanggil penaut lagi.

Penautan / Kompilasi Inkremental

Batasan utama model ini adalah bahwa pembatalan validasi node adalah masalah semua atau tidak sama sekali: saat dependensi berubah, node dependen selalu di-build ulang dari awal, meskipun ada algoritma yang lebih baik yang akan memutasi nilai lama node berdasarkan perubahan. Beberapa contoh penggunaannya:

  • Penautan inkremental
  • Jika satu file .class berubah di .jar, secara teoritis kita dapat mengubah file .jar, bukan mem-build-nya dari awal lagi.

Alasan Bazel saat ini tidak mendukung hal-hal ini dengan cara yang prinsipil (kami memiliki beberapa ukuran dukungan untuk penautan inkremental, tetapi tidak diterapkan dalam Skyframe) ada dua: kami hanya memiliki peningkatan performa yang terbatas dan sulit untuk menjamin bahwa hasil mutasi sama dengan hasil build ulang bersih, dan Google menghargai build yang dapat diulang bit demi bit.

Hingga saat ini, kita selalu dapat mencapai performa yang cukup baik dengan hanya mendekomposisi langkah build yang mahal dan mencapai revaluasi parsial dengan cara tersebut: langkah ini membagi semua class dalam aplikasi menjadi beberapa grup dan melakukan dexing secara terpisah. Dengan cara ini, jika class dalam grup tidak berubah, proses dexing tidak perlu dilakukan ulang.

Pemetaan ke konsep Bazel

Berikut adalah ringkasan kasar tentang beberapa implementasi SkyFunction yang digunakan Bazel untuk melakukan build:

  • FileStateValue. Hasil lstat(). Untuk file yang ada, kami 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 dan/atau jalur file yang di-resolve. Bergantung pada FileStateValue yang sesuai dan symlink apa pun yang perlu di-resolve (seperti FileValue untuk a/b memerlukan jalur yang di-resolve dari a dan jalur yang di-resolve dari a/b). 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. Mewakili versi file BUILD yang diuraikan. 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 konten 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 tempat target yang sesuai berada, ConfiguredTargetValues dependensi langsung, dan node khusus yang mewakili konfigurasi build.
  • ArtifactValue. Merepresentasikan file dalam build, baik itu artefak sumber atau output (artefak hampir setara dengan file, dan digunakan untuk merujuk ke file selama eksekusi langkah build yang sebenarnya). Untuk file sumber, hal ini bergantung pada FileValue node terkait, untuk artefak output, hal ini bergantung pada ActionExecutionValue tindakan apa pun yang menghasilkan artefak.
  • ActionExecutionValue. Merepresentasikan eksekusi tindakan. Bergantung pada ArtifactValues file inputnya. Tindakan yang dijalankannya saat ini terdapat dalam kunci langitnya, yang bertentangan dengan konsep bahwa kunci langit harus kecil. Kami sedang berupaya menyelesaikan perbedaan ini (perhatikan bahwa ActionExecutionValue dan ArtifactValue tidak digunakan jika kita tidak menjalankan fase eksekusi di Skyframe).