डाइनैमिक एक्सीक्यूशन, Bazel की एक सुविधा है. इसकी मदद से, एक ही ऐक्शन को लोकल और रिमोट, दोनों तरह से एक साथ शुरू किया जा सकता है. इसमें, पहले पूरा होने वाले ब्रांच का आउटपुट इस्तेमाल किया जाता है और दूसरे ब्रांच को रद्द कर दिया जाता है. यह सुविधा, रिमोट बिल्ड सिस्टम की एक्सीक्यूशन पावर और/या बड़े शेयर किए गए कैश को, लोकल एक्सीक्यूशन की कम लेटेन्सी के साथ जोड़ती है. इससे, साफ़ और इंक्रीमेंटल, दोनों तरह के बिल्ड के लिए बेहतर परफ़ॉर्मेंस मिलती है.
इस पेज पर, डाइनैमिक एक्सीक्यूशन को चालू, ट्यून, और डीबग करने का तरीका बताया गया है. अगर आपने लोकल और रिमोट, दोनों तरह के एक्सीक्यूशन सेट अप किए हैं और बेहतर परफ़ॉर्मेंस के लिए Bazel की सेटिंग में बदलाव करने की कोशिश कर रहे हैं, तो यह पेज आपके लिए है. अगर आपने अब तक रिमोट एक्सीक्यूशन सेट अप नहीं किया है, तो सबसे पहले Bazel के रिमोट एक्सीक्यूशन खास जानकारी देखें.
डाइनैमिक एक्सीक्यूशन को चालू करना
डाइनैमिक एक्सीक्यूशन मॉड्यूल, Bazel का हिस्सा है. हालांकि, डाइनैमिक एक्सीक्यूशन का इस्तेमाल करने के लिए, आपके पास Bazel के एक ही सेटअप से लोकल और रिमोट, दोनों तरह से कंपाइल करने की सुविधा होनी चाहिए.
डाइनैमिक एक्सीक्यूशन मॉड्यूल को चालू करने के लिए, Bazel को --internal_spawn_scheduler फ़्लैग पास करें. इससे dynamic नाम की एक नई एक्सीक्यूशन रणनीति जुड़ जाती है. अब इसका इस्तेमाल, उन नेमोनिक के लिए रणनीति के तौर पर किया जा सकता है जिन्हें डाइनैमिक तरीके से रन करना है. जैसे, --strategy=Javac=dynamic. डाइनैमिक एक्सीक्यूशन को किन नेमोनिक के लिए चालू करना है, यह चुनने का तरीका जानने के लिए, अगला सेक्शन देखें.
डाइनैमिक रणनीति का इस्तेमाल करने वाले किसी भी नेमोनिक के लिए, रिमोट एक्सीक्यूशन की रणनीतियां, --dynamic_remote_strategy फ़्लैग से ली जाती हैं. वहीं, लोकल रणनीतियां, --dynamic_local_strategy फ़्लैग से ली जाती हैं. --dynamic_local_strategy=worker,sandboxed पास करने पर, डाइनैमिक एक्सीक्यूशन के लोकल ब्रांच के लिए डिफ़ॉल्ट सेटिंग, वर्कर या सैंडबॉक्स वाले एक्सीक्यूशन के साथ आज़माने पर सेट हो जाती है. यह सेटिंग, इसी क्रम में लागू होती है. --dynamic_local_strategy=Javac=worker पास करने पर, सिर्फ़ Javac नेमोनिक के लिए डिफ़ॉल्ट सेटिंग बदल जाती है. रिमोट वर्शन भी इसी तरह काम करता है. दोनों फ़्लैग को कई बार तय किया जा सकता है. अगर कोई ऐक्शन लोकल तौर पर एक्ज़ीक्यूट नहीं किया जा सकता, तो उसे सामान्य तरीके से रिमोट तौर पर एक्ज़ीक्यूट किया जाता है. इसके उलट भी ऐसा ही होता है.
अगर आपके रिमोट सिस्टम में कैश मेमोरी है, तो --dynamic_local_execution_delay फ़्लैग, रिमोट सिस्टम के कैश हिट का संकेत देने के बाद, लोकल एक्सीक्यूशन में मिलीसेकंड में देरी जोड़ता है. इससे, ज़्यादा कैश हिट होने की संभावना होने पर, लोकल एक्सीक्यूशन को रन करने से बचा जा सकता है. डिफ़ॉल्ट वैल्यू 1000 मिसेकंड होती है. हालांकि, इसे ट्यून करके, कैश हिट में लगने वाले सामान्य समय से थोड़ा ज़्यादा पर सेट किया जाना चाहिए. असल समय, रिमोट सिस्टम और राउंड-ट्रिप में लगने वाले समय, दोनों पर निर्भर करता है. आम तौर पर, किसी दिए गए रिमोट सिस्टम के सभी उपयोगकर्ताओं के लिए वैल्यू एक ही होगी. हालांकि, अगर कुछ उपयोगकर्ता इतनी दूर हैं कि राउंडट्रिप लेटेन्सी जुड़ जाए, तो वैल्यू अलग हो सकती है. आम तौर पर, कैश हिट में कितना समय लगता है, यह देखने के लिए, Bazel की प्रोफ़ाइलिंग
सुविधाओं का इस्तेमाल किया जा सकता है.
डाइनैमिक एक्सीक्यूशन का इस्तेमाल, लोकल सैंडबॉक्स वाली रणनीति के साथ-साथ,
स्थायी वर्कर के साथ भी किया जा सकता है. स्थायी वर्कर, डाइनैमिक एक्सीक्यूशन के साथ इस्तेमाल किए जाने पर, सैंडबॉक्सिंग के साथ अपने-आप
रन होंगे. साथ ही, वे मल्टीप्लेक्स
वर्कर का इस्तेमाल नहीं कर सकते. Darwin और Windows सिस्टम पर, सैंडबॉक्स वाली रणनीति धीमी हो सकती है. इन सिस्टम पर सैंडबॉक्स बनाने का ओवरहेड कम करने के लिए, --reuse_sandbox_directories पास किया जा सकता है.
डाइनैमिक एक्सीक्यूशन, standalone रणनीति के साथ भी रन किया जा सकता है. हालांकि, standalone रणनीति को एक्सीक्यूट करना शुरू करते समय, आउटपुट लॉक लेना होता है. इसलिए, यह रिमोट रणनीति को पहले पूरा होने से रोकती है. --experimental_local_lockfree_output फ़्लैग, इस समस्या से बचने का एक तरीका है. इसकी मदद से, लोकल एक्सीक्यूशन सीधे आउटपुट में लिख सकता है. हालांकि, अगर रिमोट एक्सीक्यूशन पहले पूरा हो जाता है, तो लोकल एक्सीक्यूशन को रद्द कर दिया जाता है.
अगर डाइनैमिक एक्सीक्यूशन के ब्रांच में से कोई एक पहले पूरा होता है, लेकिन वह फ़ेल हो जाता है, तो पूरा ऐक्शन फ़ेल हो जाता है. यह जान-बूझकर किया जाता है, ताकि लोकल और रिमोट एक्सीक्यूशन के बीच के अंतर को नज़रअंदाज़ न किया जा सके.
डाइनैमिक एक्सीक्यूशन और उसके लॉकिंग के काम करने के तरीके के बारे में ज़्यादा जानकारी के लिए, Julio Merino की बेहतरीन ब्लॉग पोस्ट देखें
मुझे डाइनैमिक एक्सीक्यूशन का इस्तेमाल कब करना चाहिए?
डाइनैमिक एक्सीक्यूशन के लिए, रिमोट एक्सीक्यूशन सिस्टम की ज़रूरत होती है. फ़िलहाल, सिर्फ़ कैश मेमोरी वाले रिमोट सिस्टम का इस्तेमाल नहीं किया जा सकता. ऐसा इसलिए, क्योंकि कैश मेमोरी में डेटा न मिलने को, फ़ेल हुआ ऐक्शन माना जाएगा.
सभी तरह के ऐक्शन, रिमोट एक्सीक्यूशन के लिए सही नहीं होते. इसके लिए, वे ऐक्शन सबसे सही होते हैं जो लोकल तौर पर तेज़ी से रन होते हैं. जैसे, स्थायी वर्कर का इस्तेमाल करने वाले ऐक्शन. इसके अलावा, वे ऐक्शन भी सही होते हैं जो इतनी तेज़ी से रन होते हैं कि रिमोट एक्सीक्यूशन का ओवरहेड, एक्सीक्यूशन टाइम से ज़्यादा हो जाता है. स्थानीय तौर पर एक्ज़ीक्यूट किए गए हर ऐक्शन से, सीपीयू और मेमोरी के कुछ संसाधन लॉक हो जाते हैं. इसलिए, उन कैटगरी में न आने वाले ऐक्शन को रन करने से, उन ऐक्शन के एक्सीक्यूशन में देरी होती है जो उन कैटगरी में आते हैं.
वर्शन
5.0.0-pre.20210708.4 से,
परफ़ॉर्मेंस प्रोफ़ाइलिंग में वर्कर एक्सीक्यूशन के बारे में डेटा शामिल होता है. इसमें, डाइनैमिक एक्सीक्यूशन रेस हारने के बाद, काम के अनुरोध को पूरा करने में लगने वाला समय भी शामिल होता है. अगर आपको दिखता है कि डाइनैमिक एक्सीक्यूशन वर्कर थ्रेड, संसाधन हासिल करने में ज़्यादा समय ले रहे हैं या async-worker-finish में ज़्यादा समय ले रहे हैं, तो हो सकता है कि आपके पास कुछ ऐसे लोकल ऐक्शन हों जो वर्कर थ्रेड में देरी कर रहे हों.
ऊपर दी गई प्रोफ़ाइल में, आठ Javac वर्कर का इस्तेमाल किया गया है. इसमें, हमें कई Javac वर्कर रेस हारते हुए और async-worker-finish थ्रेड पर अपना काम पूरा करते हुए दिखते हैं. ऐसा इसलिए हुआ, क्योंकि वर्कर के अलावा किसी दूसरे नेमोनिक ने, वर्कर में देरी करने के लिए काफ़ी संसाधन लिए.
जब सिर्फ़ Javac को डाइनैमिक एक्सीक्यूशन के साथ रन किया जाता है, तो काम शुरू करने के बाद, शुरू किए गए सिर्फ़ आधे वर्कर रेस हारते हैं.
पहले सुझाया गया --experimental_spawn_scheduler फ़्लैग, अब काम नहीं करता.
इससे डाइनैमिक एक्सीक्यूशन चालू हो जाता है और सभी नेमोनिक के लिए dynamic को डिफ़ॉल्ट रणनीति के तौर पर सेट किया जाता है. इससे अक्सर इस तरह की समस्याएं होती थीं.
परफ़ॉर्मेंस
डाइनैमिक एक्सीक्यूशन के लिए, यह माना जाता है कि लोकल और रिमोट, दोनों तरह से काफ़ी संसाधन उपलब्ध हैं. इसलिए, कुल मिलाकर परफ़ॉर्मेंस को बेहतर बनाने के लिए, कुछ अतिरिक्त संसाधन खर्च करना सही है. हालांकि, ज़्यादा संसाधन इस्तेमाल करने से, Bazel या वह मशीन धीमी हो सकती है जिस पर यह रन होता है. इसके अलावा, रिमोट सिस्टम पर भी ज़रूरत से ज़्यादा दबाव पड़ सकता है. डाइनैमिक एक्सीक्यूशन के व्यवहार में बदलाव करने के लिए, कई विकल्प मौजूद हैं:
--dynamic_local_execution_delay फ़्लैग, रिमोट ब्रांच के शुरू होने के बाद, लोकल ब्रांच को कुछ मिलीसेकंड की देरी से शुरू करता है. हालांकि, ऐसा सिर्फ़ तब होता है, जब मौजूदा बिल्ड के दौरान रिमोट कैश हिट हुआ हो. इससे, रिमोट कैशिंग से फ़ायदा पाने वाले बिल्ड, लोकल संसाधनों को बर्बाद नहीं करते. ऐसा इसलिए, क्योंकि ज़्यादातर आउटपुट कैश मेमोरी में मिल सकते हैं. कैश मेमोरी की क्वालिटी के हिसाब से, इसे कम करने से बिल्ड की स्पीड बेहतर हो सकती है. हालांकि, इसके लिए ज़्यादा लोकल संसाधनों का इस्तेमाल करना पड़ सकता है.
--experimental_dynamic_local_load_factor , संसाधन मैनेज करने का एक एक्सपेरिमेंटल और बेहतर विकल्प है. इसकी वैल्यू 0 से 1 के बीच होती है. 0 सेट करने पर, यह सुविधा बंद हो जाती है.
जब इसे 0 से ज़्यादा वैल्यू पर सेट किया जाता है, तो Bazel, शेड्यूल किए जाने का इंतज़ार कर रहे कई ऐक्शन होने पर, लोकल तौर पर शेड्यूल किए गए ऐक्शन की संख्या को अडजस्ट करता है. इसे 1 पर सेट करने पर, उतने ऐक्शन शेड्यूल किए जा सकते हैं जितने सीपीयू उपलब्ध हैं (जैसा कि --local_cpu_resources के हिसाब से तय किया गया है). कम वैल्यू सेट करने पर, शेड्यूल किए गए ऐक्शन की संख्या भी कम हो जाती है, क्योंकि रन करने के लिए ज़्यादा ऐक्शन उपलब्ध होते हैं. यह सुनने में अजीब लग सकता है, लेकिन अच्छे रिमोट सिस्टम के साथ, कई ऐक्शन रन किए जाने पर, लोकल एक्सीक्यूशन से ज़्यादा फ़ायदा नहीं मिलता. साथ ही, लोकल सीपीयू का इस्तेमाल, रिमोट ऐक्शन को मैनेज करने के लिए बेहतर तरीके से किया जा सकता है.
--experimental_dynamic_slow_remote_time फ़्लैग, लोकल ब्रांच को शुरू करने की प्राथमिकता देता है. ऐसा तब होता है, जब रिमोट ब्रांच कम से कम इतने समय से रन हो रहा हो. आम तौर पर, हाल ही में शेड्यूल किए गए ऐक्शन को प्राथमिकता मिलती है, क्योंकि इसके रेस जीतने की संभावना सबसे ज़्यादा होती है. हालांकि, अगर रिमोट सिस्टम कभी-कभी हैंग हो जाता है या ज़्यादा समय लेता है, तो इससे बिल्ड को आगे बढ़ाया जा सकता है. यह डिफ़ॉल्ट रूप से चालू नहीं होता, क्योंकि इससे रिमोट सिस्टम से जुड़ी समस्याएं छिप सकती हैं. इन समस्याओं को ठीक किया जाना चाहिए. अगर आपने यह विकल्प चालू किया है, तो अपने रिमोट सिस्टम की परफ़ॉर्मेंस पर नज़र रखें.
--experimental_dynamic_ignore_local_signals का इस्तेमाल, रिमोट ब्रांच को तब चालू करने के लिए किया जा सकता है, जब किसी दिए गए सिग्नल की वजह से, लोकल स्पॉन बंद हो जाता है. यह
मुख्य रूप से, वर्कर के संसाधन की सीमाओं (जैसे
--experimental_worker_memory_limit_mb,
--experimental_worker_sandbox_hardening,
और
--experimental_sandbox_memory_limit_mb)) के साथ काम आता है.
इसमें, वर्कर प्रोसेस को तब बंद किया जा सकता है, जब वे बहुत ज़्यादा संसाधनों का इस्तेमाल करते हैं.
JSON ट्रेस प्रोफ़ाइल में, परफ़ॉर्मेंस से जुड़े कई ग्राफ़ शामिल होते हैं. इनकी मदद से, परफ़ॉर्मेंस और संसाधन के इस्तेमाल के बीच बेहतर तालमेल बिठाने के तरीके पहचाने जा सकते हैं.
समस्या का हल
डाइनैमिक एक्सीक्यूशन से जुड़ी समस्याएं, बहुत कम दिख सकती हैं और इन्हें डीबग करना मुश्किल हो सकता है. ऐसा इसलिए, क्योंकि ये समस्याएं, लोकल और रिमोट एक्सीक्यूशन के कुछ खास कॉम्बिनेशन में ही दिख सकती हैं.
--debug_spawn_scheduler फ़्लैग, डाइनैमिक एक्सीक्यूशन सिस्टम से अतिरिक्त आउटपुट जोड़ता है. इससे इन समस्याओं को डीबग करने में मदद मिल सकती है. समस्याओं को आसानी से दोहराने के लिए, --dynamic_local_execution_delay फ़्लैग और रिमोट बनाम लोकल जॉब की संख्या को भी अडजस्ट किया जा सकता है.
अगर आपको standalone रणनीति का इस्तेमाल करके, डाइनैमिक एक्सीक्यूशन में समस्याएं आ रही हैं, तो --experimental_local_lockfree_output के बिना रन करने की कोशिश करें. इसके अलावा, अपने लोकल ऐक्शन को सैंडबॉक्स में रन करें. इससे आपके बिल्ड की स्पीड थोड़ी कम हो सकती है (अगर आपके पास Mac या Windows है, तो ऊपर देखें). हालांकि, इससे फ़ेल होने की कुछ संभावित वजहें खत्म हो जाती हैं.