Pekerja Persisten

Laporkan masalah Lihat sumber Nightly · 7.4 . 7,3 · 7,2 · 7,1 · 7,0 · 6,5

Halaman ini membahas cara menggunakan pekerja persisten, manfaat, persyaratan, dan bagaimana pekerja memengaruhi sandboxing.

Pekerja persisten adalah proses yang berjalan lama yang dimulai oleh server Bazel, yang berfungsi sebagai wrapper untuk alat aktual (biasanya compiler), atau alat itu sendiri. Agar dapat memanfaatkan pekerja persisten, alat harus mendukung urutan kompilasi, dan wrapper harus menerjemahkan antara API alat dan format permintaan/respons yang dijelaskan di bawah. Pekerja yang sama dapat dipanggil dengan dan tanpa tanda --persistent_worker dalam build yang sama, dan bertanggung jawab untuk memulai dan berkomunikasi dengan alat dengan tepat, serta menonaktifkan pekerja saat keluar. Setiap instance pekerja ditetapkan (tetapi tidak di-chroot ke) direktori kerja terpisah yang berada di <outputBase>/bazel-workers.

Menggunakan pekerja persisten adalah strategi eksekusi yang mengurangi {i>start-up<i} tambahan, memungkinkan lebih banyak kompilasi JIT, dan memungkinkan contoh hierarki sintaksis abstrak dalam eksekusi tindakan. Strategi ini akan mencapai peningkatan ini dengan mengirimkan beberapa permintaan ke {i>checkout<i}.

Pekerja persisten diterapkan untuk beberapa bahasa, termasuk Java, Scala, Kotlin, dan lainnya.

Program yang menggunakan runtime NodeJS dapat menggunakan library helper @bazel/worker untuk menerapkan protokol pekerja.

Menggunakan pekerja persisten

Bazel 0.27 dan yang lebih tinggi menggunakan worker persisten secara default saat mengeksekusi build, meskipun jarak jauh dan eksekusi akan diprioritaskan. Untuk tindakan yang tidak mendukung pekerja persisten, Bazel kembali memulai instance alat untuk setiap tindakan. Anda dapat secara eksplisit tetapkan build Anda untuk menggunakan pekerja persisten dengan menyetel worker strategi untuk alat yang berlaku mnemonik. 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 kompilasi jauh lebih cepat, tergantung pada implementasinya. Untuk Java, build dapat berupa 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 "Memilih jumlah pekerja" bagian.

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 fitur strategi, meneruskan --experimental_spawn_scheduler penanda. Strategi ini secara otomatis memungkinkan pekerja, sehingga mereka 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 imbal balik antara pemanfaatan CPU yang tersedia dengan baik dan kompilasi JIT dan hit cache yang Anda dapatkan. Dengan lebih banyak pekerja, lebih target akan membayar biaya awal untuk menjalankan kode non-JIT dan menekan cold di cache oleh pengguna. Jika Anda memiliki sedikit target untuk dibuat, 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 dari beberapa instance pekerja bahkan 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 6 core dengan RAM 64 GB. Untuk setiap konfigurasi pekerja, lima clean build dijalankan dan yang diambil rata-rata dari empat terakhir.

Grafik peningkatan performa clean build

Gambar 1. Grafik peningkatan performa build bersih.

Untuk konfigurasi ini, dua pekerja memberikan kompilasi tercepat, meskipun hanya 14% meningkat dibandingkan dengan satu pekerja. Satu pekerja adalah pilihan 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 memberikan percepatan 3x lipat (rata-rata 20 build inkremental dengan satu build pemanasan dihapus):

Grafik peningkatan performa build inkremental

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 --worker_extra_flag untuk menentukan flag start-up kepada pekerja, yang terkunci dengan mnemonik. Contohnya, 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 mnemoni, tetapi juga untuk variasi dalam flag startup-nya. Setiap kombinasi mnemonik dan start-up flag digabungkan menjadi WorkerKey, dan untuk setiap WorkerKey hingga worker_max_instances pekerja dapat dibuat. Lihat bagian berikutnya untuk mengetahui konfigurasi tindakan juga dapat menentukan tanda penyiapan.

Anda dapat menggunakan --high_priority_workers untuk menentukan mnemonik yang harus dijalankan dengan preferensi pada prioritas normal mnemonik. Hal ini dapat membantu memprioritaskan tindakan yang selalu berada di jalur kritis. Jika ada dua atau lebih pekerja prioritas tinggi yang menjalankan permintaan, semua pekerja lain dicegah untuk 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 memberikan jaminan ketepatan yang lebih baik.

Flag --worker_quit_after_build terutama berguna untuk proses debug dan pembuatan profil. Penanda ini memaksa semua pekerja berhenti setelah build selesai. Anda juga dapat meneruskan --worker_verbose untuk mendapatkan lebih banyak output tentang apa yang dilakukan pekerja. Penanda ini tercermin dalam Kolom verbosity di WorkRequest, yang memungkinkan implementasi pekerja juga yang lebih panjang.

Pekerja menyimpan log mereka di direktori <outputBase>/bazel-workers, misalnya /tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log. Nama file menyertakan ID pekerja dan mnemoni. 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 Android Build Performance.

Mengimplementasikan 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:

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..." },
  ],
}

Worker akan menerima ini di stdin dalam format JSON newline-delimited (karena requires-worker-protocol disetel ke JSON). Pekerja kemudian melakukan tindakan, dan mengirimkan WorkResponse berformat JSON ke Bazel pada stdout-nya. Bazel kemudian menguraikan respons ini dan mengonversinya secara manual menjadi proto WorkResponse. Kepada 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 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 beberapa repositori GitHub ini, Anda dapat melihat contoh wrapper pekerja yang ditulis di Java serta di Python. Jika Anda berfungsi dalam JavaScript atau TypeScript, @bazel/paket pekerja dan contoh worker nodejs mungkin dapat membantu.

Bagaimana pengaruh pekerja terhadap sandboxing?

Menggunakan strategi worker secara default tidak menjalankan tindakan dalam 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 mungkin masih membocorkan informasi di antara permintaan secara internal, misalnya melalui cache. Menggunakan strategi dynamic mengharuskan 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 tetap valid tanpa harus membaca file.

Bahkan saat menggunakan ringkasan input untuk mencegah caching yang tidak diinginkan, proses sandbox pekerja menawarkan sandboxing yang kurang ketat dibandingkan sandbox murni, karena alat ini mempertahankan status internal lain yang telah dipengaruhi 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: