Basel का समानांतर इवैलुएशन और बढ़ोतरी वाला मॉडल.
डेटा मॉडल
डेटा मॉडल में ये आइटम शामिल होते हैं:
SkyValue
. इन्हें नोड भी कहा जाता है.SkyValues
ऐसे ऑब्जेक्ट हैं जिनमें बदलाव नहीं किया जा सकता. इनमें बिल्ड के दौरान बनाया गया सारा डेटा और बिल्ड के इनपुट शामिल होते हैं. उदाहरण के लिए: इनपुट फ़ाइलें, आउटपुट फ़ाइलें, टारगेट, और कॉन्फ़िगर किए गए टारगेट.SkyKey
.SkyValue
का रेफ़रंस देने के लिए, ऐसा छोटा नाम जिसे बदला नहीं जा सकता. उदाहरण के लिए,FILECONTENTS:/tmp/foo
याPACKAGE://foo
.SkyFunction
. नोड, उनकी कुंजियों और डिपेंडेंट नोड के आधार पर बनता है.- नोड ग्राफ़. नोड के बीच डिपेंडेंसी संबंध वाला डेटा स्ट्रक्चर.
Skyframe
. Basel के इंक्रीमेंटल इवैलुएशन फ़्रेमवर्क के लिए कोड नेम, इस पर आधारित है.
आकलन
बिल्ड पूरा करने के लिए, उस नोड का आकलन किया जाता है जो बिल्ड अनुरोध दिखाता है.
पहली बात, Baज़ल, टॉप लेवल SkyKey
की कुंजी से मिलते-जुलते SkyFunction
ढूंढता है. इसके बाद, फ़ंक्शन उन नोड के आकलन का अनुरोध करता है जो टॉप-लेवल नोड का आकलन करना चाहते हैं. बदले में, लीफ़ नोड तक पहुंचने तक SkyFunction
कॉल करते हैं. आम तौर पर, लीफ़ नोड वे होते हैं जो फ़ाइल सिस्टम में
इनपुट फ़ाइलों को दिखाते हैं. आखिर में, Basel की वैल्यू टॉप-लेवल SkyValue
की वैल्यू पर खत्म होती है. इस वैल्यू के साथ कुछ खराब असर (जैसे, फ़ाइल सिस्टम में आउटपुट फ़ाइलें) भी मिलते हैं. इसके अलावा, बिल्ड में शामिल नोड के बीच डिपेंडेंसी का डायरेक्ट एकाइक्लिक ग्राफ़ भी होता है.
SkyFunction
कई पास में SkyKeys
का अनुरोध कर सकता है. ऐसा तब होता है, जब वह उन सभी नोड को प्रोसेस नहीं कर पाता जो उसे करने के लिए ज़रूरी होते हैं. एक आसान उदाहरण, इनपुट फ़ाइल नोड की जांच करना है जो सिमलिंक होता है: फ़ंक्शन फ़ाइल को पढ़ने की कोशिश करता है और समझता है कि यह सिमलिंक है. इस तरह वह सिमलिंक के टारगेट को दिखाने वाला फ़ाइल सिस्टम नोड फ़ेच करता है. हालांकि, वह अपने-आप में एक सिमलिंक हो सकता है.
इस मामले में, ओरिजनल फ़ंक्शन को भी अपना टारगेट फ़ेच करना होगा.
फ़ंक्शन, कोड में SkyFunction
इंटरफ़ेस से दिखाए जाते हैं. साथ ही, इन्हें SkyFunction.Environment
नाम के इंटरफ़ेस से मिलने वाली सेवाओं के ज़रिए भी दिखाया जाता है. फ़ंक्शन ये काम कर सकते हैं:
env.getValue
को कॉल करके किसी दूसरे नोड के मूल्यांकन का अनुरोध करें. नोड उपलब्ध होने पर, इसकी वैल्यू दिखाई जाती है. ऐसा नहीं होने पर,null
को दिखाया जाता है. साथ ही, फ़ंक्शन कोnull
रिटर्न करना चाहिए. बाद वाले मामले में, डिपेंडेंट नोड का आकलन किया जाता है और फिर ओरिजनल नोड बिल्डर को फिर से शुरू किया जाता है. हालांकि, इस बार वहीenv.getValue
कॉल,null
के अलावा कोई अन्य वैल्यू दिखाएगा.env.getValues()
को कॉल करके, कई अन्य नोड के आकलन का अनुरोध करें. ज़रूरी तौर पर यह भी एक जैसा ही होता है. हालांकि, डिपेंडेंट नोड का आकलन साथ-साथ किया जाता है.- शुरू करने के दौरान कंप्यूटेशन (हिसाब लगाना) करना
- इसका खराब असर पड़ता है. उदाहरण के लिए, फ़ाइल सिस्टम में फ़ाइलें लिखना. इस बात का ध्यान रखना ज़रूरी है कि दो अलग-अलग फ़ंक्शन एक-दूसरे के पैरों पर चलने से बचें. आम तौर पर, ऐसे खराब असर लिखें (जहां बेज़ल से डेटा बाहर की तरफ़ जाता है) ठीक है, रीड साइड इफ़ेक्ट (जहां रजिस्टर की गई डिपेंडेंसी के बिना डेटा बेज़ल में डेटा फ़्लो होता है) ठीक नहीं है. इसकी वजह यह है कि ये बिना रजिस्ट्रेशन वाली डिपेंडेंसी हैं. इसलिए, बिल्ड में गलत बढ़ोतरी हो सकती है.
अच्छी तरह से काम करने वाला SkyFunction
लागू करने का तरीका, डिपेंडेंसी का अनुरोध करने (जैसे कि फ़ाइल सिस्टम को सीधे पढ़कर) के बजाय, किसी भी अन्य तरीके से डेटा को ऐक्सेस करने से बचाता है. इसकी वजह से, Baज़ल, पढ़ी गई फ़ाइल पर डेटा डिपेंडेंसी रजिस्टर नहीं करता. इस वजह से, बिल्ड गलत होता है.
जब किसी फ़ंक्शन में काम करने के लिए ज़रूरी डेटा हो, तो उसे पूरा होने का संकेत देने वाला एक गैर-null
वैल्यू दिखना चाहिए.
आकलन की इस रणनीति के कई फ़ायदे हैं:
- हरमैटिसिटी. अगर फ़ंक्शन सिर्फ़ दूसरे नोड के हिसाब से इनपुट डेटा का अनुरोध करते हैं, तो Basel यह गारंटी दे सकता है कि इनपुट की स्थिति एक जैसी होने पर, वही डेटा दिखाया जाएगा. अगर सभी स्काई फ़ंक्शन डिटरमिनिस्टिक हैं, तो इसका मतलब है कि पूरा बिल्ड भी डिटरमिनिस्टिक होगा.
- सही और सटीक बढ़ोतरी. अगर सभी फ़ंक्शन का पूरा इनपुट डेटा रिकॉर्ड कर लिया जाता है, तो Basel, नोड के सिर्फ़ उस सटीक सेट को अमान्य कर सकता है जिसे इनपुट डेटा में बदलाव होने पर अमान्य होने की ज़रूरत है.
- समानता. फ़ंक्शन सिर्फ़ डिपेंडेंसी का अनुरोध करके एक-दूसरे के साथ इंटरैक्ट कर सकते हैं. इसलिए, जो फ़ंक्शन एक-दूसरे पर निर्भर नहीं रहते हैं वे साथ-साथ चलाए जा सकते हैं. साथ ही, Baज़ल यह गारंटी दे सकता है कि नतीजा एक जैसा हो, जैसा कि उन्हें क्रम से चलाया जाता है.
बढ़ोतरी
फ़ंक्शन सिर्फ़ अन्य नोड के हिसाब से इनपुट डेटा को ऐक्सेस कर सकते हैं, इसलिए Baज़र, इनपुट फ़ाइलों से आउटपुट फ़ाइलों में डेटा फ़्लो का पूरा ग्राफ़ बना सकता है. साथ ही, इस जानकारी का इस्तेमाल सिर्फ़ उन नोड को फिर से बनाने के लिए कर सकता है जिन्हें फिर से बनाने की ज़रूरत है: बदली गई इनपुट फ़ाइलों के सेट का रिवर्स ट्रांज़िटिव क्लोज़.
खास तौर पर, बढ़ोतरी की दो संभावित रणनीतियां मौजूद हैं: बॉटम-अप एक और टॉप-डाउन. कौनसा सबसे सही रहेगा, यह इस बात पर निर्भर करता है कि डिपेंडेंसी ग्राफ़ कैसा दिखता है.
बॉटम-अप अमान्य होने के दौरान, ग्राफ़ बनाने और बदले गए इनपुट के सेट की जानकारी होने के बाद, वे सभी नोड अमान्य हो जाते हैं जो बदली गई फ़ाइलों पर अस्थायी रूप से निर्भर होते हैं. अगर एक जैसा टॉप-लेवल नोड फिर से बनाया जाए, तो बेहतर होगा. ध्यान दें कि बॉटम-अप अमान्य होने के लिए, पिछले बिल्ड की सभी इनपुट फ़ाइलों पर
stat()
चलाना ज़रूरी है, ताकि यह पता लगाया जा सके कि उनमें बदलाव हुआ है या नहीं. बदली गई फ़ाइलों के बारे में जानने के लिए,inotify
या इससे मिलते-जुलते तरीके का इस्तेमाल करके, इसे बेहतर बनाया जा सकता है.टॉप-डाउन अमान्य होने के दौरान, टॉप-लेवल नोड के ट्रांज़िटिव क्लोज़र को चुना जाता है. साथ ही, सिर्फ़ उन नोड को रखा जाता है जिनका ट्रांज़िटिव क्लोज़र साफ़ होता है. नोड ग्राफ़ बड़ा होने पर यह बेहतर होता है, लेकिन अगले बिल्ड के लिए सिर्फ़ इसके छोटे सबसेट की ज़रूरत होती है: बॉटम-अप अमान्य होने पर, पहले बिल्ड का बड़ा ग्राफ़ अमान्य हो जाएगा. वहीं, टॉप-डाउन अमान्य होने पर, दूसरे बिल्ड का सिर्फ़ छोटा ग्राफ़ ही दिखाया जाता है.
Baज़र, सिर्फ़ बॉटम-अप अमान्य होने पर ही कार्रवाई करता है.
ज़्यादा बढ़ोतरी करने के लिए, बेज़ल बदलाव की काट-छांट का इस्तेमाल करता है: अगर कोई नोड अमान्य हो जाता है, लेकिन उसे फिर से बनाने पर पता चलता है कि उसकी नई वैल्यू, पुरानी वैल्यू की तरह ही है, तो इस नोड में बदलाव होने की वजह से अमान्य हुए नोड "फिर से चालू" हो जाते हैं.
उदाहरण के लिए, यह तरीका काम आता है: अगर कोई व्यक्ति C++ फ़ाइल में टिप्पणी में बदलाव करता है, तो उससे जनरेट की गई .o
फ़ाइल पहले जैसी ही रहेगी. इसलिए, लिंकर को फिर से कॉल करने की ज़रूरत नहीं होगी.
इंक्रीमेंटल लिंकिंग / कंपाइलेशन
इस मॉडल की मुख्य सीमा यह है कि नोड का अमान्य होने पर कुछ भी नहीं करना होगा: डिपेंडेंसी में बदलाव होने पर, डिपेंडेंट नोड हमेशा शुरुआत से बनाया जाता है. भले ही, बदलाव के आधार पर नोड की पुरानी वैल्यू में बदलाव करने के लिए, एक बेहतर एल्गोरिदम मौजूद हो. इसके कुछ उदाहरण यहां दिए गए हैं:
- इंक्रीमेंटल लिंकिंग
- जब किसी JAR फ़ाइल में एक क्लास की फ़ाइल बदलती है, तो JAR फ़ाइल को फिर से शुरू करने के बजाय, उसी जगह बदला जा सकता है.
बेज़ल इन चीज़ों को सैद्धांतिक तरीके से सपोर्ट नहीं करने की दो वजहें हैं:
- परफ़ॉर्मेंस में सीमित सुधार हुआ.
- इस बात की पुष्टि करना मुश्किल है कि म्यूटेशन का नतीजा, किसी क्लीन रीबिल्ड के नतीजे जैसा ही होगा. साथ ही, Google की ऐसी वैल्यू बिल्ड होंगी जिन्हें दोहराया जा सकता है.
अब तक, एक महंगे बिल्ड चरण को तोड़ने और इस तरह से आंशिक रूप से फिर से आकलन करने की कोशिश करके अच्छा परफ़ॉर्म किया जा सकता था. उदाहरण के लिए, Android ऐप्लिकेशन में, सभी क्लास को एक से ज़्यादा ग्रुप में बांटा जा सकता है और उन्हें अलग-अलग ग्रुप में बांटा जा सकता है. इस तरह, अगर ग्रुप में क्लास में कोई बदलाव नहीं होता है, तो डेक्सिंग को फिर से करने की ज़रूरत नहीं है.
बेज़ेल कॉन्सेप्ट को मैप करना
यह कुंजी, SkyFunction
और SkyValue
को लागू करने के तरीके के बारे में खास जानकारी है. बेज़ल बिल्ड करने के लिए इसका इस्तेमाल करता है:
- 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 को लागू करने के बीच के संबंधों को दिखाता है: