Halaman ini membahas cara menggunakan pekerja persisten, manfaat, persyaratan, dan cara pekerja memengaruhi sandboxing.
Pekerja persisten adalah proses berdurasi panjang yang dimulai oleh server Bazel, yang berfungsi sebagai wrapper pada alat sebenarnya (biasanya compiler), atau merupakan alat itu sendiri. Untuk memanfaatkan pekerja persisten, alat ini harus mendukung tindakan kompilasi, dan wrapper harus menerjemahkan antara API alat dan format permintaan/respons yang dijelaskan di bawah. Pekerja yang sama dapat dipanggil dengan dan tanpa flag --persistent_worker
dalam build yang sama, dan bertanggung jawab untuk memulai dan berkomunikasi dengan alat dengan tepat, serta mematikan pekerja saat keluar. Setiap instance pekerja ditetapkan
(tetapi tidak di-chroot ke) direktori kerja terpisah di
<outputBase>/bazel-workers
.
Menggunakan pekerja persisten adalah strategi eksekusi yang mengurangi overhead pengaktifan, memungkinkan lebih banyak kompilasi JIT, dan memungkinkan penyimpanan dalam cache, misalnya hierarki sintaksis abstrak dalam eksekusi tindakan. Strategi ini mencapai peningkatan ini dengan mengirimkan beberapa permintaan ke proses yang berjalan lama.
Pekerja persisten diimplementasikan untuk beberapa bahasa, termasuk Java, Scala, Kotlin, dan lainnya.
Program yang menggunakan runtime NodeJS dapat menggunakan library helper @bazel/worker untuk mengimplementasikan protokol worker.
Menggunakan pekerja persisten
Bazel 0.27 dan yang lebih tinggi
menggunakan pekerja persisten secara default saat menjalankan build, meskipun eksekusi
jarak jauh lebih diutamakan. Untuk tindakan yang tidak mendukung pekerja persisten,
Bazel akan kembali memulai instance alat untuk setiap tindakan. Anda dapat menetapkan build secara eksplisit
untuk menggunakan pekerja persisten dengan menetapkan strategi
worker
untuk mnemonic alat
yang berlaku. Sebagai praktik terbaik, contoh ini menyertakan penentuan local
sebagai
fallback ke strategi worker
:
bazel build //my:target --strategy=Javac=worker,local
Menggunakan strategi pekerja, bukan strategi lokal, dapat meningkatkan kecepatan kompilasi secara signifikan, bergantung pada penerapan. Untuk Java, build dapat 2–4 kali lebih cepat, terkadang lebih untuk kompilasi inkremental. Kompilasi Bazel sekitar 2,5 kali lebih cepat dengan pekerja. Untuk mengetahui detail selengkapnya, lihat bagian "Memilih jumlah pekerja".
Jika Anda juga memiliki lingkungan build jarak jauh yang cocok dengan lingkungan build lokal, Anda dapat menggunakan strategi dinamis eksperimental, yang menjalankan eksekusi jarak jauh dan eksekusi pekerja secara bersamaan. Untuk mengaktifkan strategi
dinamis, teruskan flag
--experimental_spawn_scheduler. Strategi ini otomatis mengaktifkan pekerja, sehingga Anda tidak perlu menentukan strategi worker
, tetapi Anda tetap dapat menggunakan local
atau sandboxed
sebagai penggantian.
Memilih jumlah pekerja
Jumlah default instance pekerja per mnemonic adalah 4, tetapi dapat disesuaikan
dengan
tanda
worker_max_instances
. Ada kompromi antara penggunaan CPU yang tersedia dengan
jumlah kompilasi JIT dan hit cache yang Anda dapatkan. Dengan lebih banyak pekerja, lebih banyak
target akan membayar biaya startup untuk menjalankan kode non-JIT dan mengakses cache
dingin. Jika Anda memiliki sedikit target untuk di-build, satu pekerja dapat memberikan
kompromi terbaik antara kecepatan kompilasi dan penggunaan resource (misalnya,
lihat masalah #8586.
Flag worker_max_instances
menetapkan jumlah maksimum instance pekerja per
mnemonik dan kumpulan flag (lihat di bawah), sehingga dalam sistem campuran, Anda mungkin akan menggunakan
cukup banyak memori jika mempertahankan nilai default. Untuk build inkremental,
manfaat beberapa instance pekerja lebih kecil lagi.
Grafik ini menunjukkan waktu kompilasi dari awal untuk Bazel (target
//src:bazel
) di workstation Linux Intel Xeon 3,5 GHz dengan 6 core hyper-threaded
dan RAM 64 GB. Untuk setiap konfigurasi pekerja, lima build bersih dijalankan dan
rata-rata dari empat build terakhir diambil.
Gambar 1. Grafik peningkatan performa build bersih.
Untuk konfigurasi ini, dua pekerja memberikan kompilasi tercepat, meskipun hanya dengan peningkatan 14% dibandingkan dengan satu pekerja. Satu pekerja adalah opsi yang baik jika Anda ingin menggunakan lebih sedikit memori.
Kompilasi inkremental biasanya memberikan manfaat yang lebih besar. Build bersih relatif jarang, tetapi mengubah satu file di antara kompilasi adalah hal yang umum, terutama dalam pengembangan berbasis pengujian. Contoh di atas juga memiliki beberapa tindakan paket non-Java yang dapat mengaburkan waktu kompilasi inkremental.
Mengompilasi ulang sumber Java saja
(//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar
)
setelah mengubah konstanta string internal di
AbstractContainerizingSandboxedSpawn.java
akan mempercepat 3x lipat (rata-rata 20 build inkremental dengan satu build pemanasan
yang dihapus):
Gambar 2. Grafik peningkatan performa build inkremental.
Percepatan tergantung pada perubahan yang dilakukan. Percepatan faktor 6 diukur dalam situasi di atas saat konstanta yang biasa digunakan diubah.
Memodifikasi pekerja persisten
Anda dapat meneruskan flag
--worker_extra_flag
untuk menentukan flag startup ke pekerja, yang diberi kunci oleh mnemoni. Misalnya,
meneruskan --worker_extra_flag=javac=--debug
akan mengaktifkan proses debug hanya untuk Javac.
Hanya satu flag pekerja yang dapat ditetapkan per penggunaan flag ini, dan hanya untuk satu mnemoni.
Pekerja tidak hanya dibuat secara terpisah untuk setiap mnemoni, tetapi juga untuk
variasi dalam flag startup-nya. Setiap kombinasi flag mnemonic dan start-up
digabungkan menjadi WorkerKey
, dan untuk setiap WorkerKey
, hingga
worker_max_instances
pekerja dapat dibuat. Lihat bagian berikutnya untuk mengetahui cara
konfigurasi tindakan juga dapat menentukan flag penyiapan.
Anda dapat menggunakan flag
--high_priority_workers
untuk menentukan mnemoni yang harus dijalankan sebagai preferensi daripada mnemoni
prioritas normal. Hal ini dapat membantu memprioritaskan tindakan yang selalu berada di jalur kritis. Jika ada dua pekerja prioritas tinggi atau lebih yang menjalankan permintaan, semua pekerja lainnya akan dicegah agar tidak berjalan. Flag ini dapat digunakan beberapa kali.
Meneruskan flag
--worker_sandboxing
membuat setiap permintaan pekerja menggunakan direktori sandbox terpisah untuk semua
inputnya. Menyiapkan sandbox memerlukan waktu tambahan,
terutama di macOS, tetapi akan memberikan jaminan ketepatan yang lebih baik.
Flag
--worker_quit_after_build
terutama berguna untuk proses debug dan pembuatan profil. Flag ini memaksa semua pekerja
untuk berhenti setelah build selesai. Anda juga dapat meneruskan
--worker_verbose
untuk
mendapatkan lebih banyak output tentang apa yang dilakukan pekerja. Flag ini tercermin dalam
kolom verbosity
di WorkRequest
, sehingga implementasi pekerja juga
lebih panjang.
Pekerja menyimpan log mereka di direktori <outputBase>/bazel-workers
, misalnya
/tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log
.
Nama file mencakup ID pekerja dan mnemonik. Karena dapat ada lebih dari satu WorkerKey
per mnemoni, Anda mungkin melihat lebih dari worker_max_instances
file log untuk mnemoni tertentu.
Untuk build Android, lihat detailnya di halaman Performa Build Android.
Menerapkan pekerja persisten
Lihat halaman membuat pekerja persisten untuk mengetahui informasi selengkapnya tentang cara membuat pekerja.
Contoh ini menunjukkan konfigurasi Starlark untuk pekerja yang menggunakan JSON:
args_file = ctx.actions.declare_file(ctx.label.name + "_args_file")
ctx.actions.write(
output = args_file,
content = "\n".join(["-g", "-source", "1.5"] + ctx.files.srcs),
)
ctx.actions.run(
mnemonic = "SomeCompiler",
executable = "bin/some_compiler_wrapper",
inputs = inputs,
outputs = outputs,
arguments = [ "-max_mem=4G", "@%s" % args_file.path],
execution_requirements = {
"supports-workers" : "1", "requires-worker-protocol" : "json" }
)
Dengan definisi ini, penggunaan pertama tindakan ini akan dimulai dengan menjalankan
command line /bin/some_compiler -max_mem=4G --persistent_worker
. Permintaan
untuk mengompilasi Foo.java
akan terlihat seperti ini:
CATATAN: Meskipun spesifikasi buffering protokol menggunakan "snake case" (request_id
),
protokol JSON menggunakan "camel case" (requestId
). Dalam dokumen ini, kita akan menggunakan
camel case dalam contoh JSON, tetapi snake case saat membahas kolom
terlepas dari protokolnya.
{
"arguments": [ "-g", "-source", "1.5", "Foo.java" ]
"inputs": [
{ "path": "symlinkfarm/input1", "digest": "d49a..." },
{ "path": "symlinkfarm/input2", "digest": "093d..." },
],
}
Pekerja menerimanya di stdin
dalam format JSON yang dipisahkan baris baru (karena
requires-worker-protocol
disetel ke JSON). Pekerja kemudian melakukan tindakan,
dan mengirim WorkResponse
berformat JSON ke Bazel pada stdout-nya. Bazel kemudian
menguraikan respons ini dan mengonversinya secara manual menjadi proto WorkResponse
. Untuk
berkomunikasi dengan pekerja terkait menggunakan protobuf yang dienkode biner, bukan
JSON, requires-worker-protocol
akan disetel ke proto
, seperti ini:
execution_requirements = {
"supports-workers" : "1" ,
"requires-worker-protocol" : "proto"
}
Jika Anda tidak menyertakan requires-worker-protocol
dalam persyaratan eksekusi,
Bazel akan menetapkan komunikasi pekerja secara default untuk menggunakan protobuf.
Bazel memperoleh WorkerKey
dari mnemoni dan flag bersama, sehingga jika konfigurasi ini
memungkinkan perubahan parameter max_mem
, pekerja terpisah akan
dibuat untuk setiap nilai yang digunakan. Hal ini dapat menyebabkan konsumsi memori yang berlebihan jika
terlalu banyak variasi yang digunakan.
Setiap pekerja saat ini hanya dapat memproses satu permintaan dalam satu waktu. Fitur multiplex worker eksperimental memungkinkan penggunaan beberapa thread, jika alat yang mendasarinya bersifat multithreaded dan wrapper disiapkan untuk memahami hal ini.
Di repo GitHub ini, Anda dapat melihat contoh wrapper pekerja yang ditulis dalam Java dan Python. Jika Anda menggunakan JavaScript atau TypeScript, paket@bazel/worker dan contoh worker nodejs dapat membantu.
Bagaimana pengaruh pekerja terhadap sandboxing?
Penggunaan strategi worker
secara default tidak akan menjalankan tindakan di
sandbox, mirip dengan strategi local
. Anda dapat menetapkan
tanda --worker_sandboxing
untuk menjalankan semua pekerja di dalam sandbox, sehingga memastikan setiap
eksekusi alat hanya melihat file input yang seharusnya dimiliki. Alat ini
masih dapat membocorkan informasi antar-permintaan secara internal, misalnya melalui
cache. Menggunakan strategi dynamic
memerlukan pekerja untuk di-sandbox.
Untuk memungkinkan penggunaan cache compiler yang benar dengan pekerja, ringkasan akan diteruskan dengan setiap file input. Dengan demikian, compiler atau wrapper dapat memeriksa apakah input masih valid tanpa harus membaca file.
Meskipun menggunakan ringkasan input untuk mencegah penyimpanan dalam cache yang tidak diinginkan, pekerja dengan sandbox menawarkan sandboxing yang kurang ketat daripada sandbox murni, karena alat ini dapat menyimpan status internal lainnya yang telah terpengaruh oleh permintaan sebelumnya.
Pekerja multipleks hanya dapat di-sandbox jika penerapan pekerja mendukungnya,
dan sandboxing ini harus diaktifkan secara terpisah dengan
flag --experimental_worker_multiplex_sandboxing
. Lihat detail selengkapnya di
dokumen desain).
Bacaan lebih lanjut
Untuk informasi selengkapnya tentang pekerja persisten, lihat:
- Postingan blog pekerja persisten asli
- Deskripsi implementasi Haskell {: .external}
- Postingan blog oleh Mike Morearty {: .external}
- Pengembangan Front End dengan Bazel: Angular/TypeScript dan Pekerja Persisten dengan Asana {: .external}
- Penjelasan strategi Bazel {: .external}
- Diskusi strategi pekerja informatif di milis bazel-discuss {: .external}