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()
को कॉल करके कई अन्य नोड की जांच करने का अनुरोध करें. यह ज़रूरी है, लेकिन इस बात पर निर्भर करता है कि डिपेंडेंट नोड का आकलन एक साथ किया जाता है या नहीं.- शुरू करने के दौरान, गणना करें
- इससे खराब असर पड़ता है, जैसे कि फ़ाइल सिस्टम में फ़ाइलें लिखना. इस बात का ध्यान रखना ज़रूरी है कि दो अलग-अलग फ़ंक्शन एक-दूसरे के पैर पर न हों. आम तौर पर, खराब असर (जहां Bazel से डेटा बाहर की ओर बहता है) लिखें. खराब असर (जहां रजिस्टर किए गए डिपेंडेंसी के बिना Bazel में डेटा ट्रांसफ़र होता है) के असर को नहीं देखा जाता. ऐसा इसलिए होता है, क्योंकि उन रजिस्टरों के रजिस्ट्रेशन पर कोई असर नहीं पड़ता. साथ ही, उनकी वजह से बढ़ोतरी हो सकती है.
डिपेंडेंसी के लिए, अनुरोध करने के अलावा SkyFunction
में किसी अन्य तरीके से डेटा को ऐक्सेस नहीं करना चाहिए (जैसे कि सीधे फ़ाइल सिस्टम को पढ़ना). इसकी वजह यह है कि Bazel की मदद से, पढ़ी गई फ़ाइल में डेटा डिपेंडेंसी को रजिस्टर नहीं किया जाता. इस वजह से, गलत तरीके से बढ़ोतरी हो रही है.
जब किसी फ़ंक्शन में अपना काम करने के लिए ज़रूरी डेटा होता है, तो इसे पूरा होने का संकेत देने वाला गैर-null
मान देना चाहिए.
इस मूल्यांकन की रणनीति के कई फ़ायदे हैं:
- आनुवंशिकता. अगर फ़ंक्शन सिर्फ़ दूसरे नोड के आधार पर इनपुट डेटा का अनुरोध करते हैं, तो Bazel यह गारंटी दे सकता है कि इनपुट की स्थिति एक जैसी है. अगर सभी आकाश फ़ंक्शन तय नहीं हैं, तो इसका मतलब है कि पूरा बिल भी तय किया जाएगा.
- सही और सटीक वृद्धि. अगर सभी फ़ंक्शन का सभी इनपुट डेटा रिकॉर्ड किया जाता है, तो Bazel सिर्फ़ उन नोड के इन सेट को अमान्य कर सकता है जिन्हें इनपुट डेटा में बदलाव होने पर अमान्य होना चाहिए.
- समांतरता. फ़ंक्शन, डिपेंडेंसी का अनुरोध करके ही एक-दूसरे से इंटरैक्ट कर सकते हैं. इसलिए, ये फ़ंक्शन एक-दूसरे के साथ काम नहीं करते हैं. ये फ़ंक्शन साथ-साथ चलाए जा सकते हैं. साथ ही, Bazel यह गारंटी दे सकता है कि नतीजे एक जैसे ही होंगे और उन्हें क्रम में चलाया जाएगा.
वृद्धिशीलता
फ़ंक्शन सिर्फ़ दूसरे नोड के आधार पर इनपुट डेटा को ऐक्सेस कर सकते हैं. इसलिए, Bazel इनपुट फ़ाइलों से एक पूरा डेटा फ़्लो ग्राफ़ बनाकर, आउटपुट फ़ाइलों का इस्तेमाल कर सकता है. इस जानकारी का इस्तेमाल करके, सिर्फ़ उन नोड को फिर से बनाया जा सकता है जिन्हें असल में फिर से बनाने की ज़रूरत होती है: बदली गई इनपुट फ़ाइलों के सेट का उलटा ट्रांज़िट.
खास तौर पर, दो संभावित वृद्धि रणनीतियां मौजूद हैं: बॉटम-अप वन और टॉप-डाउन एक. इनमें से सबसे अच्छा विकल्प कौनसा है, यह इस बात पर निर्भर करता है कि डिपेंडेंसी ग्राफ़ कैसा दिखता है.
बॉटम-अप अमान्य होने के दौरान, ग्राफ़ बनाने के बाद और बदले गए इनपुट के सेट के बारे में पता चलने पर, सभी नोड अमान्य हो जाते हैं. ये नोड समय-समय पर बदली गई फ़ाइलों पर निर्भर करते हैं. यह सही होगा अगर हम जानते हैं कि वही टॉप-लेवल नोड फिर से बनाया जाएगा. ध्यान दें कि बॉटम-अप अमान्य करने की वजह से, पिछले बिल्ड की सभी इनपुट फ़ाइलों के लिए
stat()
चलाने की ज़रूरत होती है. इससे यह पता चलता है कि इन्हें बदला गया है या नहीं. बदली गई फ़ाइलों के बारे में जानने के लिए,inotify
या इससे मिलते-जुलते तरीके इस्तेमाल करके इसे बेहतर बनाया जा सकता है.टॉप-डाउन अमान्य होने के दौरान, टॉप-लेवल नोड के ट्रांज़िटिव बंद होने के समय की जांच की जाती है. साथ ही, सिर्फ़ उन नोड को रखा जाता है जिनका ट्रांज़िटिव मान साफ़ होता है. अगर बेहतर ढंग से हम जानते हैं कि मौजूदा नोड ग्राफ़ बड़ा है, लेकिन हमें अगले बिल्ड में सिर्फ़ इसके एक छोटे सबसेट की ज़रूरत है, तो टॉप-अप अमान्य करने की वजह से, पहले बिल्ड के बड़े ग्राफ़ को अमान्य कर दिया जाएगा. यह टॉप-डाउन अमान्य करने की प्रोसेस से अलग है, जो दूसरे बिल्ड के छोटे ग्राफ़ पर चलता है.
फ़िलहाल, हम सिर्फ़ बॉट अप को अमान्य बनाने की कोशिश करते हैं.
आगे बढ़ने के लिए, हम काट-छांट करने की प्रक्रिया बदलें का इस्तेमाल करते हैं: अगर कोई नोड अमान्य है, लेकिन उसे फिर से बनाने पर यह पता चलता है कि इसका नया मान वही है जो इस पुरानी वैल्यू का है, तो इस नोड में बदलाव की वजह से अमान्य नोड “फिर से बन गए” हैं.
उदाहरण के लिए, अगर किसी फ़ाइल में C++ फ़ाइल पर की गई टिप्पणी को बदला जाता है, तो यह तरीका कारगर होता है. उदाहरण के लिए, इससे जनरेट की गई .o
फ़ाइल पहले जैसी ही रहेगी. इसलिए, हमें लिंक करने वाले को फिर से कॉल करने की ज़रूरत नहीं है.
इंक्रीमेंटल लिंक करना / कंपाइलेशन
इस मॉडल की मुख्य सीमा यह है कि किसी नोड के अमान्य होने की वजह यह है कि पूरी तरह से काम नहीं किया जाएगा: जब कोई डिपेंडेंसी बदल जाती है, तो डिपेंडेंट नोड हमेशा नए सिरे से बनाया जाता है, भले ही एक बेहतर एल्गोरिदम मौजूद हो, जो बदलाव के आधार पर नोड के पुराने मान को बदल दे. कुछ उदाहरण, जहां यह काम आ सकता है:
- इंक्रीमेंटल लिंक करना
- जब कोई
.class
फ़ाइल.jar
में बदलती है, तो हम सैद्धांतिक रूप से.jar
फ़ाइल को नए सिरे से बनाने के बजाय उसमें बदलाव कर सकते हैं.
फ़िलहाल, Bazel इन चीज़ों को सैद्धांतिक तरीके से स्वीकार नहीं करता. हमारे पास इंक्रीमेंटल लिंकिंग के लिए कुछ उपाय हैं, लेकिन यह Skyframe में लागू नहीं किए गए हैं. हमारे पास परफ़ॉर्मेंस के सीमित फ़ायदे हैं. इसलिए, हम इस बात की गारंटी नहीं दे सकते थे कि बदलाव का नतीजा फिर से बनने जैसा होगा और Google की वैल्यू में थोड़ा-बहुत बदलाव हो सकता है.
अब तक, हम हमेशा किसी बिल्ड चरण को महंगा बनाकर और इस तरह से कुछ हद तक दोबारा आकलन करके बेहतर परफ़ॉर्मेंस हासिल कर सकते थे: यह ऐप्लिकेशन में मौजूद सभी क्लास को कई ग्रुप में बांट देता है और उन्हें अलग-अलग डेक्स करता है. इस तरह, अगर ग्रुप की कक्षाओं में कोई बदलाव नहीं होता, तो डीक्सिंग को फिर से तय करने की ज़रूरत नहीं होती.
Bazel से जुड़े सिद्धांतों को मैप करना
यह SkyFunction
लागू करने के कुछ तरीकों की खास जानकारी है, जिसका इस्तेमाल Bazel ने एक बिल्ड के लिए किया है:
- FileStateValue.
lstat()
का नतीजा. मौजूद फ़ाइलों के लिए, हम फ़ाइल में बदलावों का पता लगाने के लिए ज़्यादा जानकारी का भी हिसाब लगाते हैं. यह Skyframe ग्राफ़ का सबसे निचले लेवल का नोड है और इसकी कोई निर्भरता नहीं है. - FileValue. इसका इस्तेमाल किसी फ़ाइल के असल कॉन्टेंट और/या समाधान वाले पाथ के लिए किया जाता है. संबंधित
FileStateValue
और ऐसे किसी सिमलिंक पर निर्भर करता है जिन्हें हल करने की ज़रूरत है (जैसे किa/b
केFileValue
को,a
के समाधान किए गए पाथ औरa/b
के समाधान वाले पाथ की ज़रूरत होती है).FileStateValue
के बीच का अंतर अहम है, क्योंकि कुछ मामलों में (जैसे कि फ़ाइल सिस्टम ग्लोब और जैसे किsrcs=glob(["*/*.java"])
) कॉन्टेंट की असल में ज़रूरत नहीं होती है. - DirectoryListingValue. ज़रूर,
readdir()
का नतीजा. यह डायरेक्ट्री से जुड़ेFileValue
पर निर्भर करता है. - packageValue. एक BUILD फ़ाइल का पार्स किया गया वर्शन दिखाता है. इससे जुड़ी
BUILD
फ़ाइल केFileValue
पर निर्भर करता है. साथ ही, पैकेज में मौजूद ग्लोब को रिज़ॉल्व करने के लिए इस्तेमाल किए जाने वालेDirectoryListingValue
पर भी काम करता है (डेटा में मौजूदBUILD
फ़ाइल के कॉन्टेंट को दिखाने वाला डेटा स्ट्रक्चर) - कॉन्फ़िगर किया गया टारगेट वैल्यू. यह कॉन्फ़िगर किया गया टारगेट दिखाता है. यह, टारगेट के विश्लेषण के दौरान जनरेट की गई कार्रवाइयों के सेट का हिस्सा होता है. साथ ही, यह कॉन्फ़िगर किए गए टारगेट पर दी गई जानकारी पर आधारित होता है. संबंधित टारगेट में
PackageValue
, डायरेक्ट डिपेंडेंसी केConfiguredTargetValues
, और बिल्ड कॉन्फ़िगरेशन को दिखाने वाला खास नोड पर निर्भर करता है. - आर्टफ़ैक्टवैल्यू. बिल्ड में किसी फ़ाइल को दिखाता है, चाहे वह स्रोत हो या आउटपुट आर्टफ़ैक्ट, (आर्टफ़ैक्ट करीब-करीब फ़ाइलों के जैसे होते हैं) और उनका इस्तेमाल फ़ाइलों को बनाने के असल प्रोसेस के दौरान फ़ाइलों को रेफ़र करने के लिए किया जाता है. सोर्स फ़ाइलों के लिए, यह इससे जुड़े नोड के
FileValue
पर निर्भर करता है. यह आउटपुट आर्टफ़ैक्ट के लिए, किसी भी कार्रवाई केActionExecutionValue
पर निर्भर करता है. - ActionExecutionValue. कार्रवाई को एक्ज़ीक्यूट करने के बारे में बताता है. इसकी इनपुट फ़ाइलों के
ArtifactValues
पर निर्भर करता है. यह जो कार्रवाई करता है वह फ़िलहाल उसकी स्काई कुंजी में शामिल है, जो इस सिद्धांत के उलट है कि स्काई की कुंजियां छोटी होनी चाहिए. हम इस अंतर को ठीक करने की कोशिश कर रहे हैं (ध्यान दें कि अगर हम स्काईफ़्रेम पर एक्ज़ीक्यूशन फ़ेज़ नहीं चलाते हैं, तोActionExecutionValue
औरArtifactValue
इस्तेमाल नहीं करते).