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