Pekerja Persisten

Laporkan masalah Lihat sumber

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

Pekerja persisten adalah proses yang berjalan lama yang dimulai oleh server Bazel, yang berfungsi sebagai wrapper di sekitar alat sebenarnya (biasanya compiler), atau merupakan alat itu sendiri. Untuk mendapatkan manfaat dari pekerja persisten, alat ini harus mendukung pembuatan urutan kompilasi, dan wrapper harus menerjemahkan antara API alat dan format permintaan/respons yang dijelaskan di bawah ini. Pekerja yang sama mungkin dipanggil dengan dan tanpa tanda --persistent_worker di 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 bawah <outputBase>/bazel-workers.

Menggunakan pekerja persisten adalah strategi eksekusi yang mengurangi overhead memulai, memungkinkan lebih banyak kompilasi JIT, dan memungkinkan penyimpanan cache, misalnya, hierarki sintaksis abstrak dalam eksekusi tindakan. Strategi ini mencapai peningkatan tersebut dengan mengirimkan beberapa permintaan ke proses yang berjalan lama.

Pekerja persisten diimplementasikan untuk berbagai bahasa, termasuk Java, Scala, Kotlin, dan lainnya.

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

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 kembali untuk 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 penentuan local sebagai penggantian 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 implementasi. Untuk Java, build dapat 2–4 kali lebih cepat, terkadang lebih cepat untuk kompilasi inkremental. Mengompilasi 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 bersaing dengan eksekusi jarak jauh dan eksekusi pekerja. Untuk mengaktifkan strategi dinamis, teruskan flag --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 instance pekerja per mnemonik adalah 4, tetapi dapat disesuaikan dengan tanda worker_max_instances. Ada konsekuensi antara pemanfaatan CPU yang tersedia dengan baik dan jumlah kompilasi JIT dan cache ditemukan. Dengan lebih banyak pekerja, lebih banyak target akan membayar biaya start-up untuk menjalankan kode yang tidak di-JIT dan mencapai cold cache. Jika target yang akan di-build sedikit, 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 set flag (lihat di bawah), sehingga dalam sistem campuran, Anda mungkin akan menggunakan banyak memori jika tetap mempertahankan nilai default. Untuk build inkremental, manfaat beberapa instance pekerja bahkan lebih kecil.

Grafik ini menunjukkan waktu kompilasi dari awal untuk Bazel (target //src:bazel) di workstation Intel Xeon 3,5 GHz Linux 6 core hyper-thread dengan RAM 64 GB. Untuk setiap konfigurasi pekerja, lima clean build akan dijalankan dan rata-rata empat clean build terakhir akan diambil.

Grafik peningkatan performa clean build

Gambar 1. Grafik peningkatan performa clean build.

Untuk konfigurasi ini, dua pekerja memberikan kompilasi tercepat, meskipun hanya memiliki peningkatan 14% dibandingkan dengan satu pekerja. Satu pekerja adalah opsi yang bagus jika Anda ingin menggunakan lebih sedikit memori.

Kompilasi inkremental biasanya lebih menguntungkan. Build bersih relatif jarang, tetapi mengubah satu file di antara kompilasi adalah hal yang umum, 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 kecepatan 3x lipat (rata-rata 20 build inkremental dengan satu build pemanasan dihapus):

Grafik peningkatan performa build inkremental

Gambar 2. Grafik peningkatan performa build inkremental.

Peningkatan kecepatan bergantung pada perubahan yang dilakukan. Percepatan faktor 6 diukur dalam situasi di atas saat konstanta yang biasa digunakan berubah.

Mengubah pekerja persisten

Anda dapat meneruskan flag --worker_extra_flag untuk menentukan flag start-up bagi pekerja, yang dikunci oleh mnemonic. 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 mnemonik. Pekerja tidak hanya dibuat secara terpisah untuk setiap mnemonik, tetapi juga untuk variasi dalam flag start-up. Setiap kombinasi flag mnemonic dan start-up digabungkan menjadi WorkerKey, dan untuk setiap WorkerKey, maksimal worker_max_instances pekerja dapat dibuat. Lihat bagian berikutnya untuk mengetahui bagaimana konfigurasi tindakan juga dapat menentukan flag penyiapan.

Anda dapat menggunakan flag --high_priority_workers untuk menentukan mnemonik yang harus dijalankan, bukan mnemonik berprioritas normal. Hal ini dapat membantu memprioritaskan tindakan yang selalu berada di jalur penting. Jika ada dua atau lebih pekerja berprioritas tinggi yang menjalankan permintaan, semua pekerja lainnya tidak akan dapat berjalan. Flag 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 ekstra, terutama di macOS, tetapi memberikan jaminan ketepatan yang lebih baik.

Flag --worker_quit_after_build sangat 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 tindakan yang dilakukan pekerja. Flag ini tercermin dalam kolom verbosity di WorkRequest, yang memungkinkan implementasi pekerja juga 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 menyertakan ID pekerja dan mnemonik. Karena maksimum satu WorkerKey per mnemonik, Anda mungkin melihat lebih dari worker_max_instances file log untuk mnemonik tertentu.

Untuk build Android, lihat detailnya di halaman Performa Build Android.

Mengimplementasikan pekerja persisten

Lihat halaman membuat pekerja persisten untuk informasi selengkapnya tentang cara membuat pekerja.

Contoh ini menampilkan 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, kita akan menggunakan camel case dalam contoh JSON, tetapi snake case saat membahas kolom, apa pun protokolnya.

{
  "arguments": [ "-g", "-source", "1.5", "Foo.java" ]
  "inputs": [
    { "path": "symlinkfarm/input1", "digest": "d49a..." },
    { "path": "symlinkfarm/input2", "digest": "093d..." },
  ],
}

Pekerja menerima data ini di stdin dalam format JSON yang dibatasi baris baru (karena requires-worker-protocol ditetapkan ke JSON). Kemudian, pekerja akan melakukan tindakan dan mengirimkan WorkResponse berformat JSON ke Bazel pada stdout-nya. Bazel kemudian mengurai respons ini dan mengonversinya secara manual menjadi proto WorkResponse. Untuk berkomunikasi dengan pekerja terkait menggunakan protobuf berenkode biner, bukan JSON, requires-worker-protocol akan ditetapkan ke proto, seperti ini:

  execution_requirements = {
    "supports-workers" : "1" ,
    "requires-worker-protocol" : "proto"
  }

Jika requires-worker-protocol tidak disertakan dalam persyaratan eksekusi, Bazel akan menetapkan secara default komunikasi pekerja untuk menggunakan protobuf.

Bazel memperoleh WorkerKey dari mnemonik dan flag bersama, sehingga jika konfigurasi ini memungkinkan 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 pekerja nodejs mungkin 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 flag --worker_sandboxing untuk menjalankan semua pekerja di dalam sandbox, yang 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. Penggunaan strategi dynamic mengharuskan pekerja untuk di-sandbox.

Untuk memungkinkan penggunaan cache compiler yang benar bagi pekerja, ringkasan akan diteruskan bersama setiap file input. Dengan demikian, compiler atau wrapper dapat memeriksa apakah input masih valid tanpa harus membaca file.

Bahkan saat menggunakan ringkasan input untuk melindungi dari cache yang tidak diinginkan, pekerja dengan sandbox menawarkan sandbox yang kurang ketat daripada sandbox murni, karena alat ini mungkin mempertahankan status internal lain yang telah terpengaruh 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: