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

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