Bazel का पैरलल इवैल्युएशन और इंक्रीमेंटैलिटी मॉडल.
डेटा मॉडल
डेटा मॉडल में ये आइटम शामिल होते हैं:
SkyValue. इन्हें नोड भी कहा जाता है.SkyValues, ऐसे ऑब्जेक्ट होते हैं जिनमें बदलाव नहीं किया जा सकता. इनमें बिल्ड के दौरान बनाया गया सारा डेटा और बिल्ड के इनपुट शामिल होते हैं. उदाहरण के लिए: इनपुट फ़ाइलें, आउटपुट फ़ाइलें, टारगेट, और कॉन्फ़िगर किए गए टारगेट.SkyKey. यहSkyValueका रेफ़रंस देने के लिए, एक छोटा और ऐसा नाम होता है जिसमें बदलाव नहीं किया जा सकता. उदाहरण के लिए,FILECONTENTS:/tmp/fooयाPACKAGE://foo.SkyFunction. यह नोड को उनकी कुंजियों और डिपेंडेंट नोड के आधार पर बनाता है.- नोड ग्राफ़. यह एक डेटा स्ट्रक्चर है, जिसमें नोड के बीच डिपेंडेंसी के संबंध की जानकारी होती है.
Skyframe. यह Bazel के इंक्रीमेंटल इवैल्युएशन फ़्रेमवर्क का कोड नेम है.
आकलन
बिल्ड के अनुरोध को दिखाने वाले नोड का आकलन करके, बिल्ड किया जाता है.
सबसे पहले, Bazel, टॉप-लेवल SkyKey की कुंजी से जुड़ा SkyFunction ढूंढता है. इसके बाद, फ़ंक्शन उन नोड के आकलन का अनुरोध करता है जिनका इस्तेमाल करके, टॉप-लेवल नोड का आकलन किया जा सकता है. इससे अन्य SkyFunction कॉल जनरेट होते हैं. यह प्रोसेस तब तक चलती है, जब तक कि लीफ़ नोड तक न पहुंचा जाए. आम तौर पर, लीफ़ नोड वे होते हैं जो फ़ाइल सिस्टम में इनपुट फ़ाइलों को दिखाते हैं. आखिर में, Bazel को टॉप-लेवल SkyValue की वैल्यू, कुछ साइड इफ़ेक्ट (जैसे, फ़ाइल सिस्टम में आउटपुट फ़ाइलें) और बिल्ड में शामिल नोड के बीच डिपेंडेंसी का डायरेक्टेड एसाइक्लिक ग्राफ़ मिलता है.
अगर SkyFunction को अपना काम करने के लिए ज़रूरी सभी नोड के बारे में पहले से पता नहीं है, तो वह कई बार SkyKeys का अनुरोध कर सकता है. इसका एक आसान उदाहरण, किसी इनपुट फ़ाइल नोड का आकलन करना है, जो सिमलंक के तौर पर दिखता है: फ़ंक्शन, फ़ाइल को पढ़ने की कोशिश करता है. उसे पता चलता है कि यह एक सिमलंक है. इसलिए, वह सिमलंक के टारगेट को दिखाने वाले फ़ाइल सिस्टम नोड को फ़ेच करता है. हालांकि, वह खुद भी एक सिमलंक हो सकता है. ऐसे में, ओरिजनल फ़ंक्शन को भी उसका टारगेट फ़ेच करना होगा.
कोड में, फ़ंक्शन को SkyFunction इंटरफ़ेस से दिखाया जाता है. साथ ही, उन्हें
SkyFunction.Environment नाम के इंटरफ़ेस से सेवाएं मिलती हैं. फ़ंक्शन ये काम कर सकते हैं:
env.getValueको कॉल करके, किसी दूसरे नोड के आकलन का अनुरोध करना. अगर नोड उपलब्ध है, तो उसकी वैल्यू दिखाई जाती है. ऐसा न होने पर,nullदिखाया जाता है. साथ ही, फ़ंक्शन से भीnullदिखाने की उम्मीद की जाती है. दूसरे मामले में, डिपेंडेंट नोड का आकलन किया जाता है. इसके बाद, ओरिजनल नोड बिल्डर को फिर से शुरू किया जाता है. हालांकि, इस बारenv.getValueको कॉल करने पर,nullके अलावा कोई वैल्यू दिखेगी.env.getValues()को कॉल करके, कई अन्य नोड के आकलन का अनुरोध करना. इससे भी वही काम होता है. हालांकि, डिपेंडेंट नोड का आकलन एक साथ किया जाता है.- कॉल किए जाने के दौरान, कंप्यूटेशन करना
- साइड इफ़ेक्ट होना. उदाहरण के लिए, फ़ाइल सिस्टम में फ़ाइलें लिखना. यह ध्यान रखना ज़रूरी है कि दो अलग-अलग फ़ंक्शन, एक-दूसरे के काम में दखल न दें. आम तौर पर, राइट साइड इफ़ेक्ट (जिसमें डेटा, Bazel से बाहर की ओर जाता है) ठीक होते हैं. वहीं, रीड साइड इफ़ेक्ट (जिसमें डेटा, Bazel में बिना किसी रजिस्टर की गई डिपेंडेंसी के अंदर की ओर जाता है) ठीक नहीं होते. ऐसा इसलिए, क्योंकि वे रजिस्टर न की गई डिपेंडेंसी होती हैं. इस वजह से, इंक्रीमेंटल बिल्ड गलत हो सकते हैं.
SkyFunction को लागू करने के लिए, सही तरीके अपनाए जाते हैं. साथ ही, डिपेंडेंसी का अनुरोध करने के अलावा, किसी अन्य तरीके से डेटा को ऐक्सेस नहीं किया जाता. जैसे, सीधे फ़ाइल सिस्टम को पढ़कर. ऐसा इसलिए, क्योंकि इससे Bazel, पढ़ी गई फ़ाइल पर डेटा डिपेंडेंसी रजिस्टर नहीं करता. इस वजह से, इंक्रीमेंटल बिल्ड गलत हो सकते हैं.
जब किसी फ़ंक्शन के पास अपना काम करने के लिए ज़रूरी डेटा मौजूद हो, तो उसे पूरा होने की जानकारी देने के लिए, null के अलावा कोई वैल्यू दिखानी चाहिए.
आकलन की इस रणनीति के कई फ़ायदे हैं:
- हर्मेटिसिटी. अगर फ़ंक्शन, इनपुट डेटा का अनुरोध सिर्फ़ अन्य नोड पर निर्भर रहकर करते हैं, तो Bazel इस बात की गारंटी दे सकता है कि अगर इनपुट की स्थिति एक जैसी है, तो वही डेटा दिखेगा. अगर सभी स्काई फ़ंक्शन, तय तरीके से काम करते हैं, तो इसका मतलब है कि पूरा बिल्ड भी तय तरीके से काम करेगा.
- सही और सटीक इंक्रीमेंटैलिटी. अगर सभी फ़ंक्शन के सभी इनपुट डेटा को रिकॉर्ड किया जाता है, तो Bazel सिर्फ़ उन नोड को अमान्य कर सकता है जिन्हें इनपुट डेटा में बदलाव होने पर अमान्य करना ज़रूरी है.
- पैरललिज़्म. फ़ंक्शन, डिपेंडेंसी का अनुरोध करके ही एक-दूसरे के साथ इंटरैक्ट कर सकते हैं. इसलिए, एक-दूसरे पर निर्भर न रहने वाले फ़ंक्शन को एक साथ चलाया जा सकता है. साथ ही, Bazel इस बात की गारंटी दे सकता है कि नतीजे वही होंगे जो उन्हें क्रम से चलाने पर मिलते.
इंक्रीमेंटैलिटी
फ़ंक्शन, इनपुट डेटा को सिर्फ़ अन्य नोड पर निर्भर रहकर ऐक्सेस कर सकते हैं. इसलिए, Bazel, इनपुट फ़ाइलों से लेकर आउटपुट फ़ाइलों तक का पूरा डेटा फ़्लो ग्राफ़ बना सकता है. साथ ही, इस जानकारी का इस्तेमाल करके सिर्फ़ उन नोड को फिर से बिल्ड किया जा सकता है जिन्हें फिर से बिल्ड करना ज़रूरी है. जैसे, बदले गए इनपुट फ़ाइलों के सेट का रिवर्स ट्रांज़िटिव क्लोज़र.
खास तौर पर, इंक्रीमेंटैलिटी की दो रणनीतियां मौजूद हैं: बॉटम-अप और टॉप-डाउन. इनमें से कौनसी रणनीति सबसे सही है, यह इस बात पर निर्भर करता है कि डिपेंडेंसी ग्राफ़ कैसा दिखता है.
बॉटम-अप इनवैलिडेशन के दौरान, ग्राफ़ बनने और बदले गए इनपुट के सेट की जानकारी मिलने के बाद, उन सभी नोड को अमान्य कर दिया जाता है जो बदले गए फ़ाइलों पर ट्रांज़िटिव तरीके से निर्भर होते हैं. यह रणनीति तब सबसे सही होती है, जब टॉप-लेवल नोड को फिर से बिल्ड किया जाएगा. ध्यान दें कि बॉटम-अप इनवैलिडेशन के लिए, पिछले बिल्ड की सभी इनपुट फ़ाइलों पर
stat()चलाना ज़रूरी है, ताकि यह पता चल सके कि उनमें बदलाव किया गया है या नहीं. बदली गई फ़ाइलों के बारे में जानने के लिए,inotifyया इसी तरह के किसी अन्य मैकेनिज़्म का इस्तेमाल करके, इसे बेहतर बनाया जा सकता है.टॉप-डाउन इनवैलिडेशन के दौरान, टॉप-लेवल नोड के ट्रांज़िटिव क्लोज़र की जांच की जाती है. साथ ही, सिर्फ़ उन नोड को रखा जाता है जिनका ट्रांज़िटिव क्लोज़र साफ़ होता है. यह रणनीति तब बेहतर होती है, जब नोड ग्राफ़ बड़ा हो, लेकिन अगले बिल्ड के लिए उसके सिर्फ़ छोटे सबसेट की ज़रूरत हो. बॉटम-अप इनवैलिडेशन, पहले बिल्ड के बड़े ग्राफ़ को अमान्य कर देगा. वहीं, टॉप-डाउन इनवैलिडेशन, दूसरे बिल्ड के छोटे ग्राफ़ को ही अमान्य करेगा.
Bazel सिर्फ़ बॉटम-अप इनवैलिडेशन करता है.
Bazel, ज़्यादा इंक्रीमेंटैलिटी पाने के लिए, चेंज प्रूनिंग का इस्तेमाल करता है. अगर किसी नोड को अमान्य किया जाता है, लेकिन फिर से बिल्ड करने पर, यह पता चलता है कि उसकी नई वैल्यू, पुरानी वैल्यू के जैसी ही है, तो इस नोड में बदलाव की वजह से अमान्य किए गए नोड को "फिर से चालू" कर दिया जाता है.
उदाहरण के लिए, यह तब काम आता है, जब कोई व्यक्ति C++ फ़ाइल में कोई टिप्पणी बदलता है. इसके बाद, उससे जनरेट की गई .o फ़ाइल वही रहेगी. इसलिए, लिंकर को फिर से कॉल करने की ज़रूरत नहीं है.
इंक्रीमेंटल लिंकिंग / कंपाइलेशन
इस मॉडल की मुख्य सीमा यह है कि किसी नोड को अमान्य करने की प्रोसेस, पूरी तरह से या बिल्कुल नहीं वाली होती है. जब कोई डिपेंडेंसी बदलती है, तो डिपेंडेंट नोड को हमेशा शुरू से बिल्ड किया जाता है. भले ही, कोई बेहतर एल्गोरिदम मौजूद हो, जो बदलावों के आधार पर नोड की पुरानी वैल्यू में बदलाव कर सके. यहां कुछ उदाहरण दिए गए हैं, जिनमें यह रणनीति काम आ सकती है:
- इंक्रीमेंटल लिंकिंग
- जब किसी JAR फ़ाइल में कोई एक क्लास फ़ाइल बदलती है, तो उसे शुरू से फिर से बिल्ड करने के बजाय, JAR फ़ाइल में ही बदलाव किया जा सकता है.
Bazel, इन चीज़ों को सही तरीके से क्यों नहीं करता, इसकी दो वजहें हैं:
- परफ़ॉर्मेंस में सीमित सुधार हुए.
- यह पुष्टि करना मुश्किल है कि म्यूटेशन का नतीजा, साफ़ तौर पर फिर से बिल्ड करने के नतीजे के जैसा ही होगा. साथ ही, Google, ऐसे बिल्ड को महत्व देता है जिन्हें बिट-फ़ॉर-बिट दोहराया जा सके.
अब तक, किसी महंगे बिल्ड स्टेप को अलग-अलग हिस्सों में बांटकर और इस तरह, आंशिक तौर पर फिर से आकलन करके, अच्छी परफ़ॉर्मेंस हासिल की जा सकती थी. उदाहरण के लिए, Android ऐप्लिकेशन में, सभी क्लास को कई ग्रुप में बांटा जा सकता है और उन्हें अलग-अलग डेक्स किया जा सकता है. इस तरह, अगर किसी ग्रुप में क्लास में कोई बदलाव नहीं किया जाता है, तो डेक्सिंग को फिर से करने की ज़रूरत नहीं होती.
Bazel के कॉन्सेप्ट से मैपिंग करना
यहां मुख्य SkyFunction और SkyValue को लागू करने के तरीके की खास जानकारी दी गई है. Bazel, बिल्ड करने के लिए इनका इस्तेमाल करता है:
- FileStateValue. यह
lstat()का नतीजा है. मौजूदा फ़ाइलों के लिए, फ़ंक्शन अतिरिक्त जानकारी भी कंप्यूट करता है, ताकि फ़ाइल में हुए बदलावों का पता लगाया जा सके. यह Skyframe ग्राफ़ में सबसे निचले लेवल का नोड है और इसकी कोई डिपेंडेंसी नहीं है. - FileValue. इसका इस्तेमाल उन सभी चीज़ों के लिए किया जाता है जिन्हें किसी फ़ाइल के असली कॉन्टेंट या रिज़ॉल्व किए गए पाथ की ज़रूरत होती है. यह, इससे जुड़े
FileStateValueऔर उन सभी सिमलंक पर निर्भर करता है जिन्हें रिज़ॉल्व करना ज़रूरी है. जैसे,a/bके लिएFileValueकोaके रिज़ॉल्व किए गए पाथ औरa/bके रिज़ॉल्व किए गए पाथ की ज़रूरत होती है.FileValueऔरFileStateValueके बीच का अंतर ज़रूरी है, क्योंकि बाद वाले का इस्तेमाल उन मामलों में किया जा सकता है जहां फ़ाइल के कॉन्टेंट की असल में ज़रूरत नहीं होती. उदाहरण के लिए, फ़ाइल सिस्टम ग्लोब (जैसे,srcs=glob(["*/*.java"])) का आकलन करते समय , फ़ाइल के कॉन्टेंट की ज़रूरत नहीं होती. - DirectoryListingStateValue. यह
readdir()का नतीजा है.FileStateValueकी तरह, यह सबसे निचले लेवल का नोड है और इसकी कोई डिपेंडेंसी नहीं है. - DirectoryListingValue. इसका इस्तेमाल उन सभी चीज़ों के लिए किया जाता है जिन्हें किसी डायरेक्ट्री की एंट्री की ज़रूरत होती है. यह, इससे जुड़े
DirectoryListingStateValueके साथ-साथ, डायरेक्ट्री के जुड़ेFileValueपर भी निर्भर करता है. - PackageValue. यह किसी BUILD फ़ाइल के पार्स किए गए वर्शन को दिखाता है. यह, इससे जुड़ी
BUILDफ़ाइल केFileValueपर निर्भर करता है. साथ ही, यह ट्रांज़िटिव तरीके से किसी भीDirectoryListingValueपर भी निर्भर करता है जिसका इस्तेमाल पैकेज में ग्लोब को रिज़ॉल्व करने के लिए किया जाता है. पैकेज, इंटरनल तौर परBUILDफ़ाइल के कॉन्टेंट को दिखाने वाला डेटा स्ट्रक्चर होता है. - ConfiguredTargetValue. यह कॉन्फ़िगर किए गए टारगेट को दिखाता है. यह टारगेट के विश्लेषण के दौरान जनरेट की गई कार्रवाइयों के सेट और डिपेंडेंट कॉन्फ़िगर किए गए टारगेट को दी गई जानकारी का टपल होता है. यह, इससे जुड़े टारगेट के
PackageValue, डायरेक्ट डिपेंडेंसी केConfiguredTargetValues, और बिल्ड कॉन्फ़िगरेशन को दिखाने वाले खास नोड पर निर्भर करता है. - ArtifactValue. यह बिल्ड में किसी फ़ाइल को दिखाता है. यह सोर्स या आउटपुट आफ़्टिफ़ैक्ट हो सकता है. आफ़्टिफ़ैक्ट, फ़ाइलों के लगभग बराबर होते हैं. इनका इस्तेमाल, बिल्ड स्टेप के असल में एक्ज़ीक्यूट होने के दौरान, फ़ाइलों को रेफ़र करने के लिए किया जाता है. सोर्स फ़ाइलें, इससे जुड़े नोड के
FileValueपर निर्भर करती हैं. वहीं, आउटपुट आफ़्टिफ़ैक्ट, उस कार्रवाई केActionExecutionValueपर निर्भर करते हैं जिससे आफ़्टिफ़ैक्ट जनरेट होता है. - ActionExecutionValue. यह किसी कार्रवाई के एक्ज़ीक्यूशन को दिखाता है. यह, अपनी इनपुट फ़ाइलों के
ArtifactValuesपर निर्भर करता है. यह जिस कार्रवाई को एक्ज़ीक्यूट करता है वह इसकी SkyKey में शामिल होती है. यह इस कॉन्सेप्ट के उलट है कि SkyKeys छोटे होने चाहिए. ध्यान दें कि अगर एक्ज़ीक्यूशन फ़ेज़ नहीं चलता है, तोActionExecutionValueऔरArtifactValueका इस्तेमाल नहीं किया जाता है.
विज़ुअल सहायता के तौर पर, इस डायग्राम में SkyFunction को लागू करने के तरीके के बीच संबंध दिखाए गए हैं. यह जानकारी, Bazel के बिल्ड के बाद की है:
