मेरा नाम इयान किल्पैट्रिक है. मैं Blink लेआउट टीम में इंजीनियरिंग लीड हूं. साथ ही, कोजी इशी भी इस टीम में शामिल हैं. Blink टीम में शामिल होने से पहले, मैं Google Docs, Drive, और Gmail में सुविधाएं बनाने वाली फ़्रंट-एंड इंजीनियर थी. Google में "फ़्रंट-एंड इंजीनियर" की भूमिका से पहले, मैं फ़्रंट-एंड इंजीनियर थी. उस भूमिका में करीब पांच साल काम करने के बाद, मैंने Blink टीम में शामिल होने का फ़ैसला लिया. इसमें मुझे काम के दौरान C++ सीखने और Blink के बेहद जटिल कोडबेस को बेहतर बनाने में मदद मिली. आज भी, मुझे इसका सिर्फ़ एक छोटा हिस्सा ही समझ आता है. इस दौरान मुझे जो समय दिया गया उसके लिए मैं आपका आभारी हूं. मुझे इस बात से तसल्ली हुई कि "फ़्रंट-एंड इंजीनियर" की मरम्मत करके बहुत सारे लोगों ने मुझसे पहले "ब्राउज़र इंजीनियर" बन गए.
Blink की टीम में शामिल होने के दौरान, मुझे अपने पिछले अनुभव से बहुत मदद मिली. एक फ़्रंट-एंड इंजीनियर के तौर पर मुझे लगातार ब्राउज़र में गड़बड़ी, परफ़ॉर्मेंस की समस्याओं, रेंडरिंग से जुड़ी गड़बड़ियों, और अधूरी सुविधाओं का सामना करना पड़ा. LayoutNG, मेरे लिए Blink के लेआउट सिस्टम में इन समस्याओं को व्यवस्थित तरीके से ठीक करने का एक मौका था. यह कई सालों से कई इंजीनियरों के प्रयासों का नतीजा है.
इस पोस्ट में, हम आपको बताएंगे कि इस तरह के बड़े आर्किटेक्चर में बदलाव करने से, अलग-अलग तरह की गड़बड़ियों और परफ़ॉर्मेंस की समस्याओं को कैसे कम या कम किया जा सकता है.
लेआउट इंजन के आर्किटेक्चर का 30,000 फ़ुट व्यू
पहले, Blink के लेआउट ट्री को "बदलाव किया जा सकने वाला ट्री" कहा जाता था.
लेआउट ट्री में मौजूद हर ऑब्जेक्ट में इनपुट की जानकारी होती है. जैसे, पैरंट की ओर से तय किया गया उपलब्ध साइज़, किसी भी फ़्लोट की पोज़िशन, और आउटपुट की जानकारी. उदाहरण के लिए, ऑब्जेक्ट की फ़ाइनल चौड़ाई और ऊंचाई या उसकी x और y पोज़िशन.
इन ऑब्जेक्ट को रेंडर के बीच में रखा गया था. जब स्टाइल में कोई बदलाव होता है, तो हम उस ऑब्जेक्ट को 'बदलाव हुआ है' के तौर पर मार्क करते हैं. साथ ही, ट्री में उसके सभी पैरंट को भी इसी तरह मार्क करते हैं. रेंडरिंग पाइपलाइन का लेआउट फ़ेज़ चलने के बाद, हम ट्री को साफ़ करते थे. इसके बाद, किसी भी गलत ऑब्जेक्ट को ठीक करते थे. आखिर में, उन्हें सही स्थिति में लाने के लिए लेआउट चलाते थे.
हमें पता चला है कि इस आर्किटेक्चर की वजह से कई तरह की समस्याएं आ रही थीं. इनके बारे में हम यहां बताएंगे. हालांकि, सबसे पहले यह जानना ज़रूरी है कि लेआउट के इनपुट और आउटपुट क्या हैं.
इस ट्री में किसी नोड पर लेआउट चलाने से, "स्टाइल प्लस डीओएम" और पैरंट लेआउट सिस्टम (ग्रिड, ब्लॉक या फ़्लेक्स) से कोई भी पैरंट कंस्ट्रेंट, लेआउट कंस्ट्रेंट एल्गोरिदम को चलाता है और नतीजा दिखाता है.
हमारा नया आर्किटेक्चर, इस कॉन्सेप्ट मॉडल को आधिकारिक तौर पर लागू करता है. हमारे पास अब भी लेआउट ट्री है, लेकिन इसका इस्तेमाल मुख्य रूप से लेआउट के इनपुट और आउटपुट को सेव करने के लिए किया जाता है. आउटपुट के लिए, हम एक नया और बदलाव न किया जा सकने वाला ऑब्जेक्ट जनरेट करते हैं. इसे फ़्रैगमेंट ट्री कहा जाता है.
मैंने बदलाव न किए जा सकने वाले फ़्रैगमेंट ट्री के बारे में पहले बताया था. इसमें बताया गया था कि इसे इंक्रीमेंटल लेआउट के लिए, पिछले ट्री के बड़े हिस्सों का फिर से इस्तेमाल करने के लिए कैसे डिज़ाइन किया गया है.
इसके अलावा, हम उस फ़्रैगमेंट को जनरेट करने वाले पैरंट कंस्ट्रेंट ऑब्जेक्ट को भी सेव करते हैं. हम इसका इस्तेमाल कैश मेमोरी कुंजी के तौर पर करते हैं. इस बारे में हम यहां ज़्यादा जानकारी देंगे.
बदलाव न किए जा सकने वाले नए आर्किटेक्चर के हिसाब से, इनलाइन (टेक्स्ट) लेआउट एल्गोरिदम को भी फिर से लिखा गया है. यह इनलाइन लेआउट के लिए, बदलाव न किए जा सकने वाले फ़्लैट सूची के तौर पर न सिर्फ़ डेटा दिखाता है, बल्कि तेज़ी से फिर से लेआउट करने के लिए पैराग्राफ़-लेवल पर कैश मेमोरी का इस्तेमाल करता है. साथ ही, हर पैराग्राफ़ के हिसाब से आकार तय करता है, ताकि सभी एलिमेंट और शब्दों पर फ़ॉन्ट की सुविधाएं लागू की जा सकें. इसके अलावा, इसमें ICU का इस्तेमाल करके, यूनिकोड का नया द्वि-दिशात्मक एल्गोरिदम, कई सुधार, और बहुत कुछ शामिल है.
लेआउट से जुड़ी गड़बड़ियों के टाइप
लेआउट से जुड़ी गड़बड़ियां, आम तौर पर चार अलग-अलग कैटगरी में आती हैं. इनमें से हर कैटगरी की गड़बड़ी की अलग-अलग वजह होती है.
सही है या नहीं
जब हम रेंडरिंग सिस्टम की गड़बड़ियों के बारे में सोचते हैं, तो हम आम तौर पर उनके सही होने के बारे में सोचते हैं, उदाहरण के लिए: "ब्राउज़र A में X व्यवहार है, जबकि ब्राउज़र B का Y व्यवहार है", या "ब्राउज़र A और B दोनों काम नहीं कर रहे हैं". पहले हम इस काम पर काफ़ी समय बिताते थे. इस दौरान, हमें सिस्टम से लगातार जूझना पड़ता था. आम तौर पर, किसी गड़बड़ी को ठीक करने के लिए, ज़रूरत के हिसाब से बदलाव किया जाता था. हालांकि, कुछ हफ़्तों बाद पता चलता था कि हमने सिस्टम के किसी दूसरे हिस्से में भी बदलाव कर दिया है.
जैसा कि पिछली पोस्ट में बताया गया है, यह किसी बहुत ही खराब सिस्टम का संकेत है. खास तौर पर लेआउट के मामले में, हमारा किसी भी क्लास के बीच सटीक कॉन्ट्रैक्ट नहीं था. इस वजह से ब्राउज़र इंजीनियर इस बात पर निर्भर रहते हैं कि उन्हें क्या नहीं करना चाहिए या सिस्टम के किसी दूसरे हिस्से की वैल्यू को गलत तरीके से समझ पाना.
उदाहरण के लिए, एक समय पर हमारे पास फ़्लेक्स लेआउट से जुड़ी गड़बड़ियों की एक चेन थी. इसमें एक साल से ज़्यादा समय तक, करीब 10 गड़बड़ियां थीं. हर सुधार से, सिस्टम के किसी हिस्से में सही होने या परफ़ॉर्मेंस से जुड़ी समस्या होती है. इससे एक और गड़बड़ी पैदा होती है.
LayoutNG, लेआउट सिस्टम के सभी कॉम्पोनेंट के बीच के समझौते को साफ़ तौर पर बताता है. इसलिए, हमें लगता है कि हम ज़्यादा भरोसे के साथ बदलाव लागू कर सकते हैं. हमें वेब प्लैटफ़ॉर्म टेस्ट (WPT) प्रोजेक्ट से भी काफ़ी फ़ायदा मिलता है. इस प्रोजेक्ट की मदद से, कई पक्ष एक ही वेब टेस्ट सुइट में योगदान दे सकते हैं.
आज हमें पता चला है कि अगर हम अपने स्टैबल चैनल पर कोई रीग्रेशन रिलीज़ करते हैं, तो आम तौर पर WPT रिपॉज़िटरी में उससे जुड़ी कोई टेस्ट नहीं होती. साथ ही, यह कॉम्पोनेंट कॉन्ट्रैक्ट की गलतफ़हमी की वजह से नहीं होता. इसके अलावा, गड़बड़ी ठीक करने की हमारी नीति के तहत, हम हमेशा एक नया WPT टेस्ट जोड़ते हैं. इससे यह पक्का करने में मदद मिलती है कि कोई भी ब्राउज़र फिर से वही गड़बड़ी न करे.
अमान्य होने की प्रक्रिया जारी है
अगर आपको कभी कोई ऐसा गड़बड़ी का मैसेज मिला है जो ब्राउज़र विंडो का साइज़ बदलने या सीएसएस प्रॉपर्टी को टॉगल करने पर, अपने-आप ठीक हो जाता है, तो इसका मतलब है कि आपके पेज पर, गड़बड़ी की जानकारी हटाने की प्रोसेस पूरी नहीं हुई है. बदलाव किए जा सकने वाले ट्री के एक हिस्से को सही माना गया था, लेकिन पैरंट की पाबंदियों में हुए कुछ बदलाव की वजह से, यह सही आउटपुट नहीं दिखा रहा था.
ऐसा आम तौर पर, नीचे बताए गए लेआउट मोड के टू-पास (आखिरी लेआउट की स्थिति का पता लगाने के लिए, लेआउट ट्री को दो बार पैदल चलने) के साथ होता है. पहले हमारा कोड कुछ ऐसा दिखता था:
if (/* some very complicated statement */) {
child->ForceLayout();
}
इस तरह की गड़बड़ी को ठीक करने के लिए, आम तौर पर:
if (/* some very complicated statement */ ||
/* another very complicated statement */) {
child->ForceLayout();
}
इस तरह की समस्या को हल करने से, आम तौर पर परफ़ॉर्मेंस के डेटा में गंभीर गड़बड़ी आ सकती है. (ज़्यादा अमान्य होने के बारे में यहां देखें). साथ ही, इसे ठीक करने के लिए बहुत बारीकी से जानकारी दी गई है.
जैसा कि ऊपर बताया गया है, फ़िलहाल हमारे पास पैरंट की पाबंदियों का ऐसा ऑब्जेक्ट है जिसे बदला नहीं जा सकता. इसमें पैरंट लेआउट से चाइल्ड लेआउट में भेजे गए सभी इनपुट के बारे में बताया गया है. हम इसे, बदलाव न किए जा सकने वाले फ़्रैगमेंट के साथ सेव करते हैं. इस वजह से, हमने एक ऐसी जगह बनाई है जहां इन दोनों इनपुट की तुलना की जाती है. इससे यह तय किया जाता है कि बच्चे को एक और लेआउट पास करना होगा या नहीं. अंतर का पता लगाने का यह लॉजिक मुश्किल है, लेकिन इसमें सभी चीज़ें शामिल हैं. आम तौर पर, अमान्य होने से जुड़ी समस्याओं वाली इस क्लास को डीबग करने पर, मैन्युअल रूप से दोनों इनपुट की जांच की जाती है. साथ ही, यह तय किया जाता है कि इनपुट में क्या बदला गया है, जिससे किसी दूसरे लेआउट पास की ज़रूरत पड़ती है.
आम तौर पर, इस अंतर को दिखाने वाले कोड में सुधार करना आसान होता है. साथ ही, इन अलग-अलग ऑब्जेक्ट को बनाने में आसानी होती है, इसलिए इनकी यूनिट टेस्टिंग आसानी से की जा सकती है.
ऊपर दिए गए उदाहरण के लिए, अंतर का पता लगाने वाला कोड यह है:
if (width.IsPercent()) {
if (old_constraints.WidthPercentageSize()
!= new_constraints.WidthPercentageSize())
return kNeedsLayout;
}
if (height.IsPercent()) {
if (old_constraints.HeightPercentageSize()
!= new_constraints.HeightPercentageSize())
return kNeedsLayout;
}
हिस्टैरिसीस
इस तरह की गड़बड़ियां, अमान्य होने की प्रक्रिया पूरी न होने जैसी गड़बड़ियों से मिलती-जुलती हैं. असल में, पिछले सिस्टम में यह पक्का करना काफ़ी मुश्किल था कि लेआउट एक जैसा हो. इसका मतलब है कि एक ही इनपुट के साथ लेआउट को फिर से चलाने पर, एक ही आउटपुट मिलता है.
नीचे दिए गए उदाहरण में, हम सीएसएस प्रॉपर्टी को दो वैल्यू के बीच स्विच कर रहे हैं. हालांकि, इससे "बढ़ते हुए" रेक्टैंगल का नतीजा मिलता है.
हमारे पिछले बदले जा सकने वाले पेड़ की वजह से, इस तरह की गड़बड़ियों को पेश करना बहुत आसान था. अगर कोड में किसी ऑब्जेक्ट के साइज़ या पोज़िशन को गलत समय या स्टेज पर पढ़ने में गलती हुई है (उदाहरण के लिए, हमने पिछले साइज़ या पोज़िशन के बारे में "साफ़ तौर पर" नहीं बताया था), तो हम तुरंत एक छोटा सा हायस्टेरेसिस बग जोड़ देंगे. आम तौर पर, ये गड़बड़ियां टेस्टिंग में नहीं दिखतीं, क्योंकि ज़्यादातर टेस्ट किसी एक लेआउट और रेंडर पर फ़ोकस करते हैं. इससे भी अहम बात यह है कि हमें पता था कि कुछ लेआउट मोड ठीक से काम करने के लिए, रणनीति बनाने की इस रणनीति की ज़रूरत है. हमारे पास ऐसी गड़बड़ियां थीं जिनमें हम लेआउट पास हटाने के लिए ऑप्टिमाइज़ेशन करते, लेकिन एक "गड़बड़ी" शुरू की, क्योंकि सही आउटपुट पाने के लिए लेआउट मोड को दो पास की ज़रूरत होती है.
LayoutNG में, हमारे पास साफ़ तौर पर इनपुट और आउटपुट डेटा-स्ट्रक्चर होते हैं. साथ ही, पिछली स्थिति को ऐक्सेस करने की अनुमति नहीं होती. इसलिए, हमने लेआउट सिस्टम से इस तरह के गड़बड़ी को कम कर दिया है.
ज़रूरत से ज़्यादा बार अमान्य कराना और परफ़ॉर्मेंस
यह गड़बड़ी, अमान्य होने की प्रक्रिया में शामिल नहीं होने वाली गड़बड़ियों के ठीक उलट है. अक्सर, अमान्य आइटम को हटाने से जुड़ी गड़बड़ी को ठीक करते समय, हम परफ़ॉर्मेंस में गिरावट का सामना करते हैं.
हमें अक्सर परफ़ॉर्मेंस के मुकाबले सही नतीजे देने के लिए मुश्किल फ़ैसले लेने पड़ते थे. अगले सेक्शन में, हम इस बारे में ज़्यादा जानकारी देंगे कि हमने परफ़ॉर्मेंस से जुड़ी इन समस्याओं को कैसे कम किया.
दो पास वाले लेआउट और परफ़ॉर्मेंस में गिरावट
फ़्लेक्स और ग्रिड लेआउट, वेब पर लेआउट के एक्सप्रेशन में बदलाव को दिखाता है. हालांकि, ये एल्गोरिदम, ब्लॉक लेआउट एल्गोरिदम से पूरी तरह अलग थे.
ब्लॉक लेआउट (करीब सभी मामलों में) के लिए, इंजन को अपने सभी बच्चों पर सिर्फ़ एक बार लेआउट करना ज़रूरी होता है. यह परफ़ॉर्मेंस के लिए बहुत अच्छा है, लेकिन वेब डेवलपर के हिसाब से यह उतना बेहतर नहीं होता.
उदाहरण के लिए, अक्सर आपको सभी बच्चों के साइज़ को सबसे बड़े बच्चे के साइज़ तक बढ़ाना होता है. इसके लिए, पैरंट लेआउट (फ़्लेक्स या ग्रिड) एक मेज़र पास करेगा, ताकि यह पता लगाया जा सके कि हर चाइल्ड लेआउट कितना बड़ा है. इसके बाद, सभी चाइल्ड लेआउट को इस साइज़ में स्ट्रेच करने के लिए, एक लेआउट पास किया जाएगा. यह व्यवहार, फ़्लेक्स और ग्रिड लेआउट, दोनों के लिए डिफ़ॉल्ट है.
शुरुआत में, दो पास वाले लेआउट की परफ़ॉर्मेंस अच्छी थी, क्योंकि आम तौर पर लोग उन्हें ज़्यादा नेस्ट नहीं करते थे. हालांकि, ज़्यादा जटिल कॉन्टेंट आने पर, हमें परफ़ॉर्मेंस से जुड़ी गंभीर समस्याएं दिखने लगीं. अगर माप के चरण के नतीजे को कैश मेमोरी में सेव नहीं किया जाता है, तो लेआउट ट्री अपनी मेज़र स्थिति और आखिरी लेआउट की स्थिति के बीच हट जाएगा.
पहले, हम इस तरह की परफ़ॉर्मेंस में गिरावट से निपटने के लिए, फ़्लेक्स और ग्रिड लेआउट में खास कैश जोड़ने की कोशिश करते थे. यह तरीका काम कर गया और हम Flex के साथ बहुत आगे बढ़ गए. हालांकि, हमें लगातार अमान्य होने से जुड़े बग का सामना करना पड़ रहा था.
LayoutNG हमें लेआउट के इनपुट और आउटपुट, दोनों के लिए साफ़ तौर पर डेटा स्ट्रक्चर बनाने में मदद करता है. इसके अलावा, हमने मेज़रमेंट और लेआउट पास के लिए कैश मेमोरी भी बनाई है. इससे कॉम्प्लेक्सिटी, O(n) पर वापस आ जाती है. इसकी वजह से, वेब डेवलपर को अनुमानित लीनियर परफ़ॉर्मेंस मिलती है. अगर कभी ऐसा होता है कि कोई लेआउट तीन पास लेआउट कर रहा है, तो हम उस पास को भी कैश मेमोरी में सेव कर देंगे. इससे आने वाले समय में, सुरक्षित तरीके से ज़्यादा बेहतर लेआउट मोड लागू करने के मौके मिल सकते हैं. उदाहरण के लिए, रेंडरिंग में सामने आ रहा है कि किस तरह पूरे बोर्ड पर ज़्यादा से ज़्यादा सुविधाएं इस्तेमाल की जा सकती हैं. कुछ मामलों में, ग्रिड लेआउट के लिए तीन पास वाले लेआउट की ज़रूरत पड़ सकती है. हालांकि, फ़िलहाल ऐसा बहुत कम होता है.
हमें पता चला है कि जब डेवलपर को लेआउट से जुड़ी परफ़ॉर्मेंस से जुड़ी समस्याएं आती हैं, तो आम तौर पर यह, पाइपलाइन के लेआउट स्टेज के रॉ थ्रूपुट के बजाय, लेआउट के समय में होने वाली गड़बड़ी की वजह से होती है. अगर किसी छोटे बदलाव (एक एलिमेंट में एक सीएसएस प्रॉपर्टी में बदलाव) की वजह से, 50 से 100 मिलीसेकंड का लेआउट बनता है, तो हो सकता है कि यह लेआउट में एक्सपोनेंशियल गड़बड़ी हो.
खास जानकारी में
लेआउट एक बहुत ही जटिल विषय है. इस लेख में, हमने इनलाइन लेआउट ऑप्टिमाइज़ेशन (असल में, पूरा इनलाइन और टेक्स्ट सब-सिस्टम कैसे काम करता है) जैसी दिलचस्प जानकारी शामिल नहीं की है. यहां बताए गए कॉन्सेप्ट भी सिर्फ़ शुरुआती जानकारी हैं. हालांकि, हमें उम्मीद है कि हमने यह दिखा दिया है कि किसी सिस्टम के आर्किटेक्चर को व्यवस्थित तरीके से बेहतर बनाने से, लंबे समय तक ज़्यादा फ़ायदे मिल सकते हैं.
हालांकि, हम जानते हैं कि अभी काफ़ी काम करना बाकी है. हम समस्याओं की उन क्लास (परफ़ॉर्मेंस और सही होने, दोनों) के बारे में जानते हैं जिन्हें ठीक करने के लिए हम काम कर रहे हैं. और हम सीएसएस में आने वाली नई लेआउट सुविधाओं के बारे में जानने के लिए उत्साहित हैं. हमारा मानना है कि LayoutNG के आर्किटेक्चर की मदद से, इन समस्याओं को सुरक्षित और आसानी से हल किया जा सकता है.
Una Kravets की एक इमेज (आपको पता है कि कौनसी!).