Pekerja Persisten

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.
Laporkan masalah Lihat sumber

Halaman ini mencakup 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 yang sebenarnya (biasanya compiler), atau merupakan alat itu sendiri. Agar dapat memanfaatkan pekerja persisten, alat ini harus mendukung 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 flag --persistent_worker dalam build yang sama, dan bertanggung jawab untuk memulai dan berbicara dengan alat secara tepat, serta menonaktifkan pekerja saat keluar. Setiap instance pekerja ditetapkan (tetapi tidak di-root) direktori kerja terpisah dalam <outputBase>/bazel-workers.

Menggunakan pekerja persisten adalah strategi eksekusi yang mengurangi overhead startup, memungkinkan lebih banyak kompilasi JIT, dan memungkinkan caching untuk misalnya pohon hierarki abstrak dalam eksekusi tindakan. Strategi ini mencapai peningkatan ini dengan mengirimkan beberapa permintaan ke proses yang berjalan lama.

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

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

Menggunakan pekerja persisten

Bazel 0.27 dan yang lebih tinggi menggunakan pekerja persisten secara default saat menjalankan build, meskipun eksekusi jarak jauh akan diprioritaskan. Untuk tindakan yang tidak mendukung pekerja persisten, Bazel kembali untuk memulai instance alat untuk setiap tindakan. Anda dapat menetapkan build secara eksplisit untuk menggunakan pekerja persisten dengan menyetel strategi worker untuk mnemonik alat yang berlaku. Sebagai praktik terbaik, contoh ini menyertakan penentuan local sebagai penggantian strategi worker:

bazel build //my:target --strategy=Javac=worker,local

Menggunakan strategi worker, bukan strategi lokal, dapat meningkatkan kecepatan kompilasi secara signifikan, bergantung pada implementasi. Untuk Java, build dapat 2–4 kali lebih cepat, terkadang lebih untuk kompilasi inkremental. Mengompilasi Bazel sekitar 2,5 kali lebih cepat dengan pekerja. Untuk detail selengkapnya, lihat bagian "Memilih jumlah pekerja".

Jika juga memiliki lingkungan build jarak jauh yang cocok dengan lingkungan build lokal, Anda dapat menggunakan strategi dinamis eksperimental, yang akan menjalankan eksekusi jarak jauh dan eksekusi pekerja. Untuk mengaktifkan strategi dinamis, teruskan flag --experiment_spawn_scheduler. Strategi ini otomatis memungkinkan pekerja, sehingga Anda tidak perlu menentukan strategi worker, tetapi Anda tetap dapat menggunakan local atau sandboxed sebagai pengganti.

Memilih jumlah pekerja

Jumlah default instance pekerja per mnemonik adalah 4, tetapi dapat disesuaikan dengan tanda worker_max_instances. Ada beberapa kompromi antara memanfaatkan CPU yang tersedia dengan baik dan jumlah kompilasi JIT dan cache yang Anda dapatkan. Dengan lebih banyak pekerja, lebih banyak target akan membayar biaya awal untuk menjalankan kode non-JIT dan mencapai cache yang dingin. Jika Anda memiliki sejumlah kecil 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 pada akhirnya dapat menggunakan banyak memori jika 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) pada workstation Intel Xeon 3,5 GHz Linux 6-thread dengan thread inti dengan RAM 64 GB. Untuk setiap konfigurasi pekerja, lima build bersih dijalankan dan rata-rata empat build terakhir diambil.

Grafik peningkatan performa build bersih

Gambar 1. Grafik peningkatan performa build bersih.

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

Kompilasi inkremental biasanya memiliki manfaat yang lebih besar. Clean build relatif jarang terjadi, tetapi mengubah satu file di antara kompilasi sangatlah umum, khususnya dalam pengembangan berbasis pengujian. Contoh di atas juga memiliki beberapa tindakan pengemasan non-Java yang dapat menaungi waktu kompilasi inkremental.

Kompilasi 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 (rata-rata 20 build inkremental dengan satu build pemanasan yang dihapus):

Grafik peningkatan performa build inkremental

Gambar 2. Grafik peningkatan performa build inkremental.

Percepatan bergantung pada perubahan yang dilakukan. Percepatan faktor 6 diukur dalam situasi di atas ketika konstanta yang biasa digunakan diubah.

Memodifikasi pekerja persisten

Anda dapat meneruskan flag --worker_extra_flag untuk menentukan flag startup ke pekerja, yang dikuncikan oleh mnemonic. Misalnya, meneruskan --worker_extra_flag=javac=--debug akan mengaktifkan proses debug hanya untuk Java. Hanya satu flag worker 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 mereka. 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 mnemonik yang harus dijalankan lebih diutamakan daripada mnemonik prioritas normal. Hal ini dapat membantu memprioritaskan tindakan yang selalu ada di jalur penting. Jika ada dua atau beberapa pekerja berprioritas tinggi yang menjalankan permintaan, semua pekerja lain tidak akan dapat dijalankan. Flag ini dapat digunakan beberapa kali.

Meneruskan flag --worker_sandboxing membuat setiap permintaan pekerja menggunakan direktori sandbox terpisah untuk semua inputnya. Penyiapan sandbox memerlukan waktu tambahan, 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 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 untuk menjadi lebih panjang.

Pekerja menyimpan lognya dalam direktori <outputBase>/bazel-workers, misalnya /tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log. Nama file mencakup ID pekerja dan mnemonik. Karena hanya boleh ada lebih dari 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 Android Build.

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 kemudian akan terlihat seperti ini:

CATATAN: Meskipun spesifikasi buffering protokol menggunakan "kasus ular" (request_id), protokol JSON menggunakan "camel case" (requestId). Dalam dokumen ini, kita akan menggunakan kasus camel dalam contoh JSON, tetapi kasus ular saat berbicara tentang kolom terlepas dari protokolnya.

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

Pekerja menerima ini di stdin dalam format JSON yang dibatasi baris baru (karena requires-worker-protocol ditetapkan ke JSON). Pekerja kemudian melakukan tindakan, dan mengirimkan WorkResponse berformat JSON ke Bazel di stdout-nya. Bazel kemudian mengurai 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 default untuk menggunakan protobuf.

Bazel mendapatkan WorkerKey dari mnemonic dan flag bersama. Jadi, jika konfigurasi ini memungkinkan perubahan parameter max_mem, pekerja terpisah akan dibuat untuk setiap nilai yang digunakan. Hal ini dapat menyebabkan penggunaan 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 multithread dan wrapper disiapkan untuk memahami hal ini.

Dalam repositori GitHub ini, Anda dapat melihat contoh wrapper pekerja yang ditulis di Java serta di Python. Jika Anda bekerja di JavaScript atau TypeScript, paket @bazel/Worker dan contoh pekerja nodejs mungkin berguna.

Bagaimana pengaruh pekerja terhadap sandbox?

Menggunakan strategi worker secara default tidak 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 ada. Alat ini mungkin masih membocorkan informasi antarpermintaan secara internal, misalnya melalui cache. Penggunaan strategi dynamic mengharuskan pekerja untuk di-sandbox.

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

Bahkan saat menggunakan digest input untuk melindungi dari cache yang tidak diinginkan, pekerja dengan sandbox menawarkan sandbox yang kurang ketat dibandingkan dengan sandbox murni, karena alat ini dapat mempertahankan status internal lainnya yang telah terpengaruh oleh permintaan sebelumnya.

Pekerja multipleks hanya dapat di-sandbox jika implementasi pekerja mendukungnya, dan sandbox 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: