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