العمال الدائمون

تتناول هذه الصفحة كيفية استخدام العاملين الدائمين، والمزايا، والمتطلبات، وكيفية تأثير العاملين على وضع الحماية.

العامل الدائم هو عملية طويلة الأمد بدأه خادم Bazel الذي يعمل كأداة تضمين حول الأداة الفعلية (عادةً ما يكون مجمِّعًا) أو هي الأداة نفسها. للاستفادة من العاملين المتواصلين، يجب أن تدعم الأداة تنفيذ سلسلة من عمليات التجميع، ويجب أن تتم ترجمة البرنامج بين واجهة برمجة تطبيقات الأداة وتنسيق الطلب/الاستجابة الموضح أدناه. يمكن استدعاء العامل نفسه باستخدام العلامة --persistent_worker في الإصدار نفسه أو بدونه، وتقع على عاتقك مسؤولية بدء الأداة والتحدّث معها بشكل مناسب، بالإضافة إلى إغلاق العاملين عند الخروج. يتم تخصيص دليل عمل منفصل ضمن <outputBase>/bazel-workers (ولكن يتعذّر اقتطاعه) لكل نسخة افتراضية للعامل.

ويُعدّ استخدام العاملين الدائمين استراتيجية تنفيذ تقلّل من النفقات الناشئة، وتسمح بتجميع المزيد من التجميع أثناء التنفيذ، وتتيح التخزين المؤقت لأمثلة على أشجار البنية المجرّدة في تنفيذ الإجراء. وتنفّذ هذه الاستراتيجية هذه التحسينات عن طريق إرسال طلبات متعددة إلى عملية طويلة الأمد.

ويتمّ تنفيذ العمّال الدائمين للغات متعدّدة، بما في ذلك Java Scala وKotlinوغير ذلك.

يمكن أن تستخدم البرامج التي تستخدم وقت تشغيل NodeJS مكتبة المساعدة @bazel/work لتنفيذ بروتوكول العامل.

استخدام العاملين الدائمين

البازل 0.27 والإصدارات الأحدث يستخدم العاملين المستمرين بشكل تلقائي عند تنفيذ الإصدارات، على الرغم من أن التنفيذ عن بُعد له الأولوية. وبالنسبة إلى الإجراءات التي لا تدعم العاملين الدائمين، تعود Bazel إلى بدء مثيل أداة لكل إجراء. ويمكنك ضبط الإصدار صراحةً لاستخدام عاملين دائمين من خلال ضبط workerالاستراتيجية للأدوات القابلة للتطبيق. في إطار أفضل الممارسات، يشمل هذا المثال تحديد local كخطة بديلة للاستراتيجية worker:

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

يمكن أن يؤدي استخدام استراتيجية العاملين بدلاً من الاستراتيجية المحلية إلى زيادة سرعة التجميع بشكلٍ كبير، اعتمادًا على طريقة التنفيذ. بالنسبة إلى Java، يمكن أن تكون الإصدارات أسرع بمرتين أو أربع مرات، وفي بعض الأحيان المزيد من التجميع. تُعدّ عملية تجميع "بازل" حوالي 2.5 ضعف السرعة في العمل. لمزيد من التفاصيل، يُرجى الاطّلاع على القسم "اختيار عدد العاملين".

إذا كانت لديك بيئة تصميم عن بُعد تتطابق مع بيئة الإصدار المحلي لديك، يمكنك استخدام الاستراتيجية الديناميكية التجريبية التي تعمل على سباق التنفيذ عن بُعد وتنفيذ العمّال. لتفعيل الاستراتيجية الديناميكية، مرِّر العلامة --experimental_spawn_scheduler. تعمل هذه الاستراتيجية تلقائيًا على تفعيل العاملين، لذلك لا داعي لتحديد استراتيجية worker، ولكن لا يزال بإمكانك استخدام local أو sandboxed كعناصر احتياطية.

اختيار عدد العاملين

العدد التلقائي لمثيلات العاملين لكل تذكير هو 4، ولكن يمكن تعديله باستخدام العلامة worker_max_instances. هناك مفاضلة بين الاستفادة الجيدة من وحدات المعالجة المركزية المتاحة ومقدار تجميع JIT ونتائج ذاكرة التخزين المؤقت التي تحصل عليها. مع زيادة عدد العاملين، سيدفع عدد أكبر من الأهداف المستهدفة تكاليف بدء تشغيل الرمز غير المضغوط ووصوله إلى ذاكرات التخزين المؤقت الباردة. إذا كان لديك عدد قليل من الاستهدافات المطلوب إنشاؤها، يمكن للعامل الواحد تقديم أفضل المفاضلة بين سرعة التجميع واستخدام الموارد (على سبيل المثال، يمكنك الاطّلاع على المشكلة رقم 8586). تحدّد العلامة worker_max_instances الحدّ الأقصى لعدد مثيلات العمّال لكلّ تذكير ومجموعة رموز (يُرجى الاطّلاع على المعلومات أدناه)، لذا في حال استخدام نظام مختلط، قد ينتهي بك الأمر باستخدام مساحة كبيرة من الذاكرة إذا احتفظت بالقيمة التلقائية. وبالنسبة إلى الإصدارات التدريجية، تكون فائدة مثيلات العمّال المتعددة أصغر بكثير.

يعرض هذا الرسم البياني أوقات التجميع من البداية لتطبيق Bazel (الهدف //src:bazel) على محطة عمل Intel Xeon بسرعة 3.5 غيغاهرتز ومترابطة الخيط مع ذاكرة وصول عشوائي (RAM) بسعة 64 غيغابايت. لكل من تكوينات العاملين، يتم تشغيل خمسة إصدارات نظيفة ويتم أخذ متوسط آخر أربعة إصدارات.

رسم بياني لتحسين الأداء في إنشاءات سليمة

الشكل 1. رسم بياني لتحسين الأداء في إنشاءات سليمة.

في عملية الضبط هذه، يمنح العاملان أسرع عملية تجميع، ولكن مع التحسّن بنسبة 14% فقط مقارنةً بعامل واحد. ويعدّ اختيار عامل واحد خيارًا جيدًا إذا كنت تريد استخدام مساحة أقل من الذاكرة.

عادةً ما يكون للتجميع المتزايد فائدة أكبر. نادرًا ما تكون الإصدارات النظيفة نسبيًا، ولكن تغيير ملف واحد بين عمليات التجميع شائع، لا سيما في عملية التطوير التجريبي. يحتوي المثال السابق أيضًا على بعض إجراءات التغليف التي لا تتبع جافا، والتي يمكن أن تحجب وقت التجميع المتزايد.

تؤدي إعادة تجميع مصادر Java فقط (//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar) بعد تغيير ثابت سلسلة داخلية في AbstractcontainerizingSandboxedSpawn.java إلى زيادة السرعة بمقدار 3 أضعاف (متوسط 20 إصدارًا متزايدًا مع بناء إصدار استعداد واحد):

رسم بياني لتحسين الأداء في الإصدارات المتزايدة

الشكل 2. رسم بياني لتحسين الأداء في الإصدارات المتزايدة.

تعتمد السرعة على التغيير الذي يتم إجراؤه. يتم قياس تسارع العامل 6 في الحالة أعلاه عند تغيير ثابت شائع الاستخدام.

تعديل العاملين الدائمين

يمكنك تمرير العلامة --worker_extra_flag للإبلاغ عن العمّال الذين يبادرون باستخدام الخدمة، ويتم تشغيل مفاتيح الأمان بتقنية "التذكّر". على سبيل المثال، يؤدي تفعيل --worker_extra_flag=javac=--debug إلى تفعيل تصحيح الأخطاء في Javac فقط. ويمكن وضع علامة عامل واحد فقط لكل استخدام من هذه العلامة، وللعلم فقط. لا يتم إنشاء العاملين بشكل منفصل لكل تذكير فقط، ولكن أيضًا بالنسبة إلى المتغيرات في علامات بدء التشغيل. يتم دمج كل مجموعة من العلامات التي تؤدي إلى الذاكرة أو بدء التشغيل في WorkerKey، ويمكن إنشاء ما يصل إلى worker_max_instances عامل في كل WorkerKey. يُرجى الاطّلاع على القسم التالي للتعرّف على كيفية تحديد إعداد الإجراء أيضًا لعلامات الإعداد.

يمكنك استخدام العلامة --high_priority_workers لتحديد رسالة تذكيرية يجب أن تكون مفضَّلة وفقًا للأولوية العادية. يمكن أن يساعد ذلك في ترتيب أولويات الإجراءات التي تكون دائمًا في المسار الحرج. وإذا كان هناك عاملان أو أكثر من العاملين ذوي الأولوية العالية ينفّذون الطلبات، يتم منع جميع العاملين الآخرين من العمل. يمكن استخدام هذه العلامة عدة مرات.

يؤدي تمرير علامة --worker_sandboxing إلى جعل كل طلب عامل يستخدم دليلاً منفصلاً لوضع الحماية لكل إدخالاته. يستغرق إعداد وضع الحماية بعض الوقت الإضافي، خاصةً على نظام التشغيل macOS، ولكنه يضمن ضمانًا أفضل.

يكون وضع علامة --worker_quit_after_build مفيدًا في المقام الأول لتصحيح الأخطاء ووضع العلامات. تفرض هذه العلامة على جميع العاملين الخروج بعد تصميم المبنى. ويمكنك أيضًا تمرير --worker_verbose للحصول على مزيد من النتائج حول الإجراءات التي يتّخذها العاملون. تنعكس هذه العلامة في الحقل verbosity في WorkRequest، ما يسمح أيضًا بتنفيذ العاملين بشكل أكبر

يخزّن العاملون السجلّات التابعة لهم في الدليل <outputBase>/bazel-workers، على سبيل المثال /tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log. يتضمن اسم الملف رقم تعريف العامل والتذكير. بما أنه يمكن إضافة أكثر من ملف WorkerKey واحد لكل تذكير، يمكن أن ترى أكثر من worker_max_instances ملف سجلّ مرتبط بتذكير معيّن.

بالنسبة إلى إصدارات Android، يُرجى الاطّلاع على التفاصيل في صفحة أداء التطبيق من Android.

تنفيذ العاملين الدائمين

اطّلِع على صفحة إنشاء عاملين ثابتين للحصول على معلومات عن كيفية جعل العاملين عاملين.

يوضح هذا المثال إعداد Starlark لعامل يستخدم 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" }
)

من خلال هذا التعريف، يبدأ الاستخدام الأول لهذا الإجراء بتنفيذ سطر الأوامر /bin/some_compiler -max_mem=4G --persistent_worker. فسيبدو طلب تجميع Foo.java كما يلي:

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

يتلقّى العامل هذا على stdin بتنسيق JSON المحدّد بفواصل (لأنّه تم ضبط requires-worker-protocol على JSON). بعد ذلك، يُنفِّذ العامل الإجراء، ويرسل WorkResponse بتنسيق JSON إلى Bazel على stdout. بعد ذلك، يُجري Bazel تحليلاً لهذا الرد ويحوّله يدويًا إلى نموذج Proto لـ WorkResponse. للتواصل مع العامل المرتبط باستخدام نموذج أولي ثنائي الاتجاه بدلاً من JSON، سيتم ضبط requires-worker-protocol على proto كما يلي:

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

إذا لم تُدرِج requires-worker-protocol في متطلبات التنفيذ، ستُعدِّل Bazel التواصل مع العاملين لاستخدام Protobuf.

يستمد البازل علامة WorkerKey من الرمز الإيضاحي والعلامات المشتركة، بحيث إذا سمح هذا الضبط بتغيير المعلَمة max_mem، يتم وضع عامل منفصل لكل قيمة مستخدمة. ويمكن أن يؤدي ذلك إلى استهلاك زائد للذاكرة في حال استخدام أشكال كثيرة جدًا.

يمكن لكل عامل حاليًا معالجة طلب واحد فقط في كل مرة. تسمح ميزة العاملين المتعددين التجريبيين باستخدام سلاسل محادثات متعددة، إذا كانت الأداة الأساسية متعددة سلاسل محادثات وتم إعداد برنامج التضمين لفهم ذلك.

في جدول GitHub هذا، يمكنك الاطّلاع على أمثلة من برامج تضمين العاملين مكتوبة بلغة Java وأيضًا في Python. إذا كنت تعمل باستخدام JavaScript أو TypeScript، قد يكون من المفيد استخدام @bazel/work package ومثال عامل nodejs.

كيف يؤثر العاملون في وضع الحماية؟

لا يؤدي استخدام الاستراتيجية worker تلقائيًا إلى تنفيذ الإجراء في وضع الحماية، تمامًا مثل استراتيجية local. يمكنك وضع علامة --worker_sandboxing على تشغيل جميع العاملين في وضع الحماية، مع التأكد من أن كل تنفيذ للأداة لا يعرض سوى ملفات الإدخال التي من المفترض أن تكون بها. وقد تستمر الأداة في تسرب المعلومات بين الطلبات داخليًا، من خلال ذاكرة التخزين المؤقت مثلاً. ويتطلّب استخدام الاستراتيجية dynamic أن يكون العمّال في وضع الحماية.

للسماح بالاستخدام الصحيح لذاكرة التخزين المؤقت للمحوّل مع العاملين، يتم تمرير الملخّص مع كل ملف إدخال. وبالتالي، يمكن للباحث أو برنامج التضمين التحقق مما إذا كان الإدخال لا يزال صالحًا بدون الحاجة إلى قراءة الملف.

حتى عند استخدام ملخصات الإدخال للحماية من التخزين المؤقت غير المرغوب فيه، يوفر العاملون في وضع الحماية وضع حماية أقل صرامة من وضع الحماية الكامل، لأن الأداة قد تحتفظ بحالة داخلية أخرى تأثرت بالطلبات السابقة.

يمكن استخدام وضع الحماية المتعدد في وضع الحماية فقط إذا كان العامل يدعمه، ويجب تفعيل وضع الحماية هذا بشكل منفصل باستخدام العلامة --experimental_worker_multiplex_sandboxing. اطّلع على مزيد من التفاصيل في مستند التصميم.

قراءة المزيد

لمزيد من المعلومات عن العاملين الدائمين، راجع: