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