डाइनैमिक एक्ज़ीक्यूशन

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