स्काईफ़्रेम

किसी समस्या की शिकायत करें सोर्स देखें Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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 यह गारंटी दे सकता है कि इनपुट स्टेटस एक जैसा होने पर, एक ही डेटा वापस मिलेगा. अगर सभी Sky फ़ंक्शन डिटरमिनिस्टिक हैं, तो इसका मतलब है कि पूरा बिल्ड भी डिटरमिनिस्टिक होगा.
  • सही और बेहतर इंक्रीमेंटलिटी. अगर सभी फ़ंक्शन का पूरा इनपुट डेटा रिकॉर्ड किया जाता है, तो 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 पर निर्भर करता है. साथ ही, यह पैकेज में ग्लोब (BUILD फ़ाइल के कॉन्टेंट को अंदरूनी तौर पर दिखाने वाला डेटा स्ट्रक्चर) को हल करने के लिए इस्तेमाल किए गए किसी भी DirectoryListingValue पर भी ट्रांज़िशन करता है
  • ConfiguredTargetValue. कॉन्फ़िगर किए गए टारगेट को दिखाता है. यह टारगेट के विश्लेषण के दौरान जनरेट की गई कार्रवाइयों के सेट का ट्यूपल होता है. साथ ही, यह कॉन्फ़िगर किए गए उन टारगेट को दी गई जानकारी भी दिखाता है जो इस पर निर्भर करते हैं. यह उस PackageValue पर निर्भर करता है जिसमें संबंधित टारगेट मौजूद है, डायरेक्ट डिपेंडेंसी की ConfiguredTargetValues, और बिल्ड कॉन्फ़िगरेशन को दिखाने वाले खास नोड पर.
  • ArtifactValue. यह बिल्ड में मौजूद किसी फ़ाइल को दिखाता है. भले ही, वह सोर्स या आउटपुट आर्टफ़ैक्ट हो. आर्टफ़ैक्ट, फ़ाइलों के बराबर होते हैं और बिल्ड के चरणों को लागू करने के दौरान, फ़ाइलों को रेफ़र करने के लिए इनका इस्तेमाल किया जाता है. सोर्स फ़ाइलों के लिए, यह उससे जुड़े नोड के FileValue पर निर्भर करता है. आउटपुट आर्टफ़ैक्ट के लिए, यह उस कार्रवाई के ActionExecutionValue पर निर्भर करता है जिससे आर्टफ़ैक्ट जनरेट होता है.
  • ActionExecutionValue. किसी कार्रवाई के होने की जानकारी देता है. यह इनपुट फ़ाइलों के ArtifactValues पर निर्भर करता है. फ़िलहाल, यह कार्रवाई स्काई बटन में शामिल होती है. यह इस कॉन्सेप्ट के उलट है कि स्काई बटन छोटे होने चाहिए. हम इस अंतर को ठीक करने पर काम कर रहे हैं. ध्यान दें कि अगर हम Skyframe पर एक्सीक्यूशन फ़ेज़ नहीं चलाते हैं, तो ActionExecutionValue और ArtifactValue का इस्तेमाल नहीं किया जाता.