डाइनैमिक एक्सीक्यूशन, 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 है, तो ऊपर देखें). हालांकि, इससे फ़ेल होने की कुछ संभावित वजहें खत्म हो जाती हैं.