Halaman ini membahas cara menggunakan pekerja persisten, manfaat, persyaratan, dan cara pekerja memengaruhi sandbox.
Pekerja persisten adalah proses berjalan lama yang dimulai oleh server Bazel, yang berfungsi sebagai wrapper pada alat aktual (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 secara tepat, serta mematikan pekerja saat keluar. Setiap instance pekerja ditetapkan
(tetapi tidak di-chroot ke) direktori kerja yang terpisah dalam
<outputBase>/bazel-workers
.
Menggunakan worker persisten adalah strategi eksekusi yang mengurangi overhead start-up, memungkinkan lebih banyak kompilasi JIT, dan memungkinkan penyimpanan 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 baru 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 secara eksplisit
menetapkan build untuk menggunakan pekerja persisten dengan menetapkan
strategi worker
untuk mnemonik alat
yang berlaku. Sebagai praktik terbaik, contoh ini mencakup penetapan local
sebagai
penggantian strategi worker
:
bazel build //my:target --strategy=Javac=worker,local
Menggunakan strategi pekerja, bukan strategi lokal, dapat meningkatkan kecepatan kompilasi secara signifikan, bergantung pada implementasinya. Untuk Java, build bisa 2–4 kali lebih cepat, terkadang lebih cepat untuk kompilasi inkremental. Menyusun Bazel sekitar 2,5 kali lebih cepat dengan pekerja. Untuk 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 memacu eksekusi jarak jauh dan eksekusi pekerja. Untuk mengaktifkan strategi
dinamis, teruskan tanda
--experimental_spawn_scheduler. Strategi ini otomatis mengaktifkan pekerja, sehingga tidak perlu
menentukan strategi worker
, tetapi Anda masih dapat menggunakan local
atau sandboxed
sebagai
penggantian.
Memilih jumlah pekerja
Jumlah default worker instance per mnemonik adalah 4, tetapi dapat disesuaikan
dengan
flag
worker_max_instances
. Ada imbal balik antara penggunaan CPU yang tersedia dengan baik serta
jumlah kompilasi JIT dan hit cache yang Anda dapatkan. Dengan lebih banyak pekerja, lebih banyak target akan membayar biaya awal untuk menjalankan kode non-JIT dan menekan cold cache. Jika target yang perlu di-build dalam jumlah kecil, 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 flag yang ditetapkan (lihat di bawah), sehingga dalam sistem campuran, Anda pada akhirnya dapat 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
hyper-thread hyper-thread
dengan RAM 64 GB. Untuk setiap konfigurasi pekerja, lima clean build akan dijalankan dan rata-rata dari empat build terakhir akan diambil.
Gambar 1. Grafik peningkatan performa clean build.
Untuk konfigurasi ini, dua pekerja memberikan kompilasi tercepat, meskipun hanya mengalami peningkatan 14% dibandingkan dengan satu pekerja. Satu pekerja adalah opsi yang bagus jika Anda ingin menggunakan lebih sedikit memori.
Kompilasi inkremental biasanya lebih bermanfaat. Build bersih relatif jarang, tetapi mengubah satu file antar-kompilasi adalah hal biasa, khususnya dalam pengembangan berbasis pengujian. Contoh di atas juga memiliki beberapa tindakan pengemasan non-Java yang dapat membayangi 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
memberikan percepatan 3x lipat (rata-rata 20 build inkremental dengan satu build pemanasan
dihapus):
Gambar 2. Grafik peningkatan performa build inkremental.
Percepatan tergantung pada perubahan yang dilakukan. Percepatan faktor 6 diukur dalam situasi di atas jika konstanta yang umum digunakan diubah.
Memodifikasi pekerja persisten
Anda dapat meneruskan flag
--worker_extra_flag
untuk menentukan flag start-up kepada pekerja, yang dikunci dengan mnemonik. Misalnya,
meneruskan --worker_extra_flag=javac=--debug
akan mengaktifkan proses debug untuk Javac saja.
Hanya satu tanda pekerja yang dapat disetel per penggunaan tanda ini, dan hanya untuk satu mnemonik.
Pekerja tidak hanya dibuat secara terpisah untuk setiap mnemonik, tetapi juga untuk
variasi dalam flag start-up-nya. Setiap kombinasi flag mnemonik dan start-up
digabungkan menjadi WorkerKey
, dan maksimal
worker_max_instances
pekerja dapat dibuat untuk setiap WorkerKey
. Lihat bagian berikutnya untuk mengetahui cara
konfigurasi tindakan juga dapat menentukan tanda penyiapan.
Anda dapat menggunakan
flag --high_priority_workers
untuk menentukan mnemonik yang harus dijalankan sebagai preferensi daripada mnemonik
prioritas normal. Hal ini dapat membantu memprioritaskan tindakan yang selalu ada di jalur
penting. Jika ada dua atau beberapa pekerja prioritas tinggi yang mengeksekusi permintaan, semua
pekerja lainnya tidak akan dapat berjalan. Tanda ini dapat digunakan beberapa kali.
Meneruskan flag --worker_sandboxing
akan 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 dapat
menjadi 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 setiap mnemonik bisa terdapat lebih dari satu
WorkerKey
, Anda mungkin akan melihat lebih dari worker_max_instances
file log untuk mnemonik tertentu.
Untuk build Android, lihat detailnya di halaman Android Build Performance.
Mengimplementasikan pekerja persisten
Lihat halaman membuat pekerja persisten untuk 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 mengeksekusi
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, kami akan menggunakan
camel case dalam contoh JSON, tetapi saat membahas kolom,
terlepas dari protokol yang digunakan.
{
"arguments": [ "-g", "-source", "1.5", "Foo.java" ]
"inputs": [
{ "path": "symlinkfarm/input1", "digest": "d49a..." },
{ "path": "symlinkfarm/input2", "digest": "093d..." },
],
}
Pekerja akan menerima ini di stdin
dalam format JSON yang dibatasi baris baru (karena
requires-worker-protocol
ditetapkan 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 ditetapkan 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 agar menggunakan protobuf.
Bazel memperoleh WorkerKey
dari flag mnemonik dan flag bersama, sehingga jika konfigurasi ini
mengizinkan perubahan parameter max_mem
, pekerja terpisah akan
muncul 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 pekerja multipleks eksperimental memungkinkan penggunaan beberapa thread, jika alat yang mendasarinya adalah multi-thread dan wrapper disiapkan untuk memahami hal ini.
Dalam repo GitHub ini, Anda dapat melihat contoh wrapper pekerja yang ditulis di Java serta di Python. Jika Anda menggunakan JavaScript atau TypeScript, paket@bazel/worker dan contoh worker nodejs dapat membantu.
Bagaimana pengaruh pekerja terhadap sandbox?
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, yang memastikan setiap eksekusi alat hanya melihat file input yang seharusnya dimilikinya. Alat ini
masih dapat membocorkan informasi antar-permintaan secara internal, misalnya melalui
cache. Penggunaan strategi dynamic
mengharuskan pekerja di-sandbox.
Untuk memungkinkan penggunaan cache compiler yang benar dengan pekerja, ringkasan akan diteruskan bersama setiap file input. Dengan demikian, compiler atau wrapper dapat memeriksa apakah input masih valid tanpa harus membaca file.
Meskipun menggunakan ringkasan input untuk mencegah caching yang tidak diinginkan, pekerja yang di-sandbox menawarkan sandbox yang kurang ketat dibandingkan sandbox murni karena alat ini dapat mempertahankan status internal lain yang telah dipengaruhi oleh permintaan sebelumnya.
Pekerja multipleks hanya dapat di-sandbox jika implementasi pekerja mendukungnya, dan sandbox ini harus diaktifkan secara terpisah dengan tanda --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 penerapan yang memiliki {: .external}
- Postingan blog oleh Mike Morearty {: .external}
- Pengembangan Front End dengan Bazel: Angular/TypeScript dan Persistent Worker dengan Asana {: .external}
- Penjelasan strategi Bazel {: .external}
- Diskusi strategi pekerja yang informatif pada milis bazel-discuss {: .external}