हामीले हाम्रो प्लगइनको कार्यसम्पादन कसरी २००% ले बढायौं

लेखक

मा

,

गत फेब्रुअरी २, २०१७ मा, एन टेलरले CodeInWP मा 12 Things We Need to See From WordPress Page Builders in 2017 + Who Already Gets It Right शीर्षकको लेख लेख्नुभयो। हामीले एक वर्षभन्दा बढी समयदेखि निरन्तर Page Builder Sandwich मा सुधार गर्दै आएका छौँ: हामी अनुसन्धान गर्छौँ, हाम्रा ग्राहकहरूको कुरा सुन्छौँ, त्यसैले Page Builder Sandwich ले CodeInWP को “Who Got it Right” लेखमा स्थान पाउनेछ भन्ने हामीले सोचेका थियौँ। तर, पर्फर्मेन्सका कारण हामी पर्न सकेनौँ। ?

त्यसैले हामीले एनलाई यसबारे सोध्यौँ, र उहाँले जवाफ दिनुभयो:

…मैले यसको निःशुल्क संस्करण केही समय चलाएर हेरेँ। यसले धेरै सम्भावनाहरू देखाउँछ: धेरै मोड्युलहरू, निकै सहज UI, उत्कृष्ट WYSIWYG अनुभव, सबै कुरा सजिलै यताउता सार्न सकिन्छ। तर, पृष्ठमा लगभग १० वटा मोड्युलहरू भएपछि, सम्पादन गति (editing speed) कम भएको कारण यसमा काम जारी राख्न गाह्रो भयो…

उहाँका अनुसार, Page Builder Sandwich मा पर्फर्मेन्ससम्बन्धी समस्याहरू छन्। र हामी सधैँ प्रतिक्रियाहरू सुन्ने भएकाले, हामीले यसको जाँच गर्‍यौँ।

पर्फर्मेन्सको परीक्षण

हामीलाई PBS को पर्फर्मेन्स परीक्षण गर्ने उपाय चाहिएको थियो; PBS मुख्यतया जाभास्क्रिप्ट (Javascript) मार्फत ब्राउजरमा चल्ने भएकाले, Chrome को Timeline profiler यस कामका लागि सही उपकरण थियो। टाइमलाइन प्रोफाइलरले रनटाइमको समयमा वेबपेजमा कुन-कुन प्रक्रियाहरू भइरहेका छन् भनी पहिचान र मापन गर्न सक्छ, जुन गतिको अवरोधहरू (speed bottlenecks) पत्ता लगाउनका लागि उपयुक्त छ।

हामीले पहिले सबैभन्दा सरल चरणहरू परीक्षण गर्नुपर्छ भन्ने सोच्यौँ – हाम्रो माउसलाई धेरै तत्वहरू (elements) र पङ्क्तिहरू (rows) माथि चलाउने। सरल छ, होइन त? यति सामान्य कुरामा हामीले पर्फर्मेन्ससम्बन्धी समस्याहरूको सामना गर्नुपर्दैन भन्नेमा हामी विश्वस्त थियौँ। दुर्भाग्यवश, हामी गलत थियौँ।

हामीले रेकर्ड बटन थिचेपछि, स्क्रिनमा करिब २ सेकेन्डसम्म माउस घुमायौँ, त्यसपछि स्टप बटन थिच्यौँ। प्रोफाइलरबाट प्राप्त टाइमलाइन यहाँ दिइएको छ:

हेर्नुहोस् त! यो पर्फर्मेन्स अवरोधहरूले भरिभराउ छ!

ग्राफमा धेरै कुराहरू भइरहेका छन्, तर सबैभन्दा स्पष्ट समस्या के हो भने यसमा धेरै बैजनी (purple) क्षेत्रहरू छन् (जो हुनुहुँदैनथ्यो)। त्यसमाथि, यी बैजनी क्षेत्रहरू निकै फराकिला देखिन्छन्, र सबै ग्राफको उच्चतम बिन्दुमा पुगेको देखिन्छ। तपाईंले यो पनि देख्नुहुनेछ कि ग्राफ सधैँ भरिएको छ, र बैजनी क्षेत्रहरू लगभग कहिल्यै शून्यमा झर्दैनन्। यस्तो लाग्छ कि ब्राउजर सधैँ केही न केही गर्न व्यस्त छ, र लगभग कहिल्यै आराम गरिरहेको छैन।

तर ती सबै बैजनी क्षेत्रहरूको वास्तविक अर्थ के हो?

प्रोफाइलरले हामीलाई ती प्रक्रियाहरूलाई reflows भनिन्छ भनी जानकारी दियो। Google Developer साइट:

दस्तावेजमा भएका तत्वहरूको स्थिति र ज्यामिति पुनः गणना गर्नका लागि प्रयोग हुने वेब ब्राउजर प्रक्रियाको नाम Reflow हो.. reflow ब्राउजरमा प्रयोगकर्तालाई अवरोध गर्ने (user-blocking) कार्य हो।

यसको मतलब PBS मा माउस घुमाउँदा अत्यधिक रिफ्लोहरू ट्रिगर हुन्छन्। झन् नराम्रो कुरा के हो भने, प्रत्येक रिफ्लोले ब्राउजरलाई केही क्षणका लागि रोक्छ ताकि यसले आफ्नो गणना पूरा गर्न सकोस्। धेरै रिफ्लो हुनुको अर्थ ब्राउजर धेरै पटक रोकिनु हो, र अनुभव अझ बढी janky (अड्किने खालको) बन्छ। सरल शब्दमा भन्नुपर्दा, धेरै बैजनी क्षेत्रहरू = नराम्रो।

तसर्थ एनको भनाइ सही साबित भयो: हामीसँग वास्तवमै एउटा ठूलो पर्फर्मेन्स अवरोध छ, त्यसैले हाम्रो टोली यसलाई तुरुन्तै समाधान गर्न लागिपर्‍यो।

हाम्रो रिफ्लो / ज्याङ्क (Jank) समस्याको समाधान

अरू केही गर्नुअघि, वास्तवमा के कारणले गर्दा यो रिफ्लो भइरहेको छ भनी पत्ता लगाउन हामीले केही अनुसन्धान गर्नुपर्ने थियो। हामीले थाहा पायौँ कि, जाभास्क्रिप्टका धेरै सामान्य कुराहरूले ब्राउजरमा रिफ्लो उत्पन्न गराउन सक्छन्।

DOM तत्वको चौडाइ (width), उचाइ (height) वा स्थिति (position) प्राप्त गर्न प्रयोग गर्न सकिने लगभग कुनै पनि प्रकार्य (function) ले रिफ्लो ट्रिगर गर्न सक्छ। पल आइरिशले जाभास्क्रिप्टमा यो कार्य गर्न सक्ने सबै गुण (properties) र प्रकार्यहरूको विस्तृत सूची लेख्नुभएको छ। नराम्रो खबर के छ भने Page Builder Sandwich ले यी धेरै गुणहरू र प्रकार्यहरू प्रयोग गर्छ, जसले गर्दा रिफ्लो हुन्छ:

  • elem.getBoundingClientRect()
  • window.getComputedStyle()
  • window.scrollY
  • र अरू धेरै…

समाधान #१: महँगा रिफ्लो प्रक्रियाहरूलाई क्यास (Cache) गर्ने

सबैभन्दा स्पष्ट समाधान ती गुणहरू र प्रकार्यहरू प्रयोग नगर्नु हो। तर, PBS तिनीहरूमा धेरै निर्भर छ र हामी तिनीहरूबिना काम गर्न सक्दैनौँ।

हामीसँग बाँकी रहेको समाधान भनेको ती प्रकार्यहरूमा हुने कलहरूलाई सकेसम्म कम गर्नु हो। यहाँ कठिन कुरा के थियो भने हामीले ती गुण र प्रकार्यहरूलाई PBS का विभिन्न भागहरूमा धेरै पटक प्रयोग गरेका थियौँ, र धेरैजसो समय, हामीले तिनीहरूलाई एउटै DOM तत्वमा पटक-पटक प्रयोग गरेका थियौँ। यसलाई समाधान गर्न, हामीले प्रत्येक समस्याग्रस्त गुण/प्रकार्यका लागि हाम्रो आफ्नै क्यासिङ प्रणाली (caching mechanism) बनायौँ।

उदाहरणका लागि, getBoundingClientRect प्रकार्यले हामीलाई DOM तत्वको हालको माथिल्लो (top), बायाँ (left), चौडाइ (width), उचाइ (height), दायाँ (right), र तल्लो (bottom) पिक्सेल मापनहरू दिन्छ। हामी PBS मा तत्वको रूपरेखा कहाँ कोर्ने भन्ने स्थान गणना गर्न सधैँ यो प्रकार्य प्रयोग गर्छौँ। यसका लागि हामीले बनाएको क्यासिङ प्रकार्य यहाँ छ:

// Cache: Our client rects are kept here, we can use this to clear them later.
var elemsWithBoundingRects = [];

window.pbsGetBoundingClientRect = function( element ) {

	// Check if we already got the client rect before.
	if ( ! element._boundingClientRect ) {

		// If not, get it then store it for future use.
		element._boundingClientRect = element.getBoundingClientRect();
		elemsWithBoundingRects.push( element );
	}
	return element._boundingClientRect;
};
​

getBoundingClientRect कल गर्नुको सट्टा, अब हामीले pbsGetBoundingClientRect प्रयोग गर्‍यौँ। अब हरेक पटक client rect सोधिँदा, हामी त्यो मानलाई भविष्यको सन्दर्भका लागि सुरक्षित गर्छौँ, जसले गर्दा रिफ्लो ट्रिगरहरूको सङ्ख्या सीमित हुन्छ।

संग्रहित client rect हरू अमान्य (invalidated) हुँदा क्यास खाली गर्ने तरिका पनि हामीलाई चाहिएको थियो। प्रयोगकर्ताले पृष्ठ स्क्रोल गर्दा वा स्क्रिनमा तत्वहरू थप्दा/परिवर्तन गर्दा/हटाउँदा संग्रहित client rect को मानहरू परिवर्तन हुन्छन्। त्यसैले हामीले क्यास खाली गर्ने प्रकार्य बनायौँ जसलाई हामीले ताजा client rect मानहरू आवश्यक पर्दा कल गर्न सक्छौँ:

var clearClientRects = function() {
	var i;
	for ( i = 0; i < elemsWithBoundingRects.length; i++ ) {
		if ( elemsWithBoundingRects[ i ] ) {
			elemsWithBoundingRects[ i ]._boundingClientRect = null;
		}
	}
	elemsWithBoundingRects = [];
};​

हामीले सामान्यतया प्रयोग गर्ने अन्य महँगा प्रकार्य कलहरूका लागि पनि यही क्यासिङ संयन्त्र अपनायौँ।

समाधान #२: FastDom प्रयोग गरेर Reads र Writes लाई समूहबद्ध गर्ने

समाधान #१ कार्यान्वयन गर्दा PBS को पर्फर्मेन्स बढ्यो, तर त्यो पर्याप्त थिएन।

गुगलमा अझ बढी खोज्दै जाँदा, हामीले Wilson Page को Preventing ‘layout thrashing’ सम्बन्धी लेख भेट्यौँ। विल्सन भन्छन्:

Layout Thrashing (रिफ्लो) तब हुन्छ जब जाभास्क्रिप्टले तीव्र रूपमा DOM बाट धेरै पटक लेख्छ (writes), अनि पढ्छ (reads), जसले गर्दा डकुमेन्ट रिफ्लोहरू हुन्छन्।

स्पष्ट रूपमा, विशिष्ट रिफ्लो-ट्रिगर गर्ने गुणहरू र प्रकार्यहरूमा तपाईंको कलहरू सीमित गर्नु मात्र पर्याप्त छैन। जब तपाईं DOM मा लगातार धेरै पटक “reads” र “writes” गर्नुहुन्छ, यसले ब्राउजर रिफ्लो पनि ट्रिगर गर्छ। विल्सनको साइटबाट सीधा लिइएको एक सामान्य परिदृश्य यहाँ छ:

// Read
var h1 = element1.clientHeight;

// Write (invalidates layout)
element1.style.height = (h1 * 2) + 'px';

// Read (triggers reflow)
var h2 = element2.clientHeight;

// Write (invalidates layout)
element2.style.height = (h2 * 2) + 'px';

// Read (triggers reflow)
var h3 = element3.clientHeight;

// Write (invalidates layout)
element3.style.height = (h3 * 2) + 'px';

// Document reflows at end of frame​

माथिको उदाहरणले ३ वटा रिफ्लोहरू ट्रिगर गर्छ किनभने एक पटक लेखेपछि (write) पूरै पृष्ठको आयाम अमान्य हुन्छ। यसको लागि सुझाइएको समाधान भनेको कोडलाई पुन: क्रमबद्ध गरी सबै reads र writes लाई एकै ठाउँमा समूहबद्ध गर्नु हो ताकि सम्पूर्ण कुराले केवल १ ब्राउजर रिफ्लो मात्र ट्रिगर गरोस्:

// Read
var h1 = element1.clientHeight;
var h2 = element2.clientHeight;
var h3 = element3.clientHeight;

// Write (invalidates layout)
element1.style.height = (h1 * 2) + 'px';
element2.style.height = (h2 * 2) + 'px';
element3.style.height = (h3 * 2) + 'px';

// Document reflows at end of frame​

माथिको स्निपेटले समस्या समाधान गर्न सजिलो देखाउँछ। तर वास्तवमा, PBS मा, रिफ्लो ट्रिगर गर्ने कुराहरू पढ्ने र लेख्ने कार्य PBS को जीवनचक्रको समयमा धेरै फरक ठाउँहरूमा र धेरै फरक समयमा हुन्छ। जाभास्क्रिप्ट कोडका हजारौँ लाइनहरू पुनर्गठन गर्नु सम्भव छैन।

हाम्रो भाग्यले, विल्सनले आफ्नो लेखमा एउटा समाधान पनि दिएका थिए। उनले ती reads र writes लाई एकै ठाउँमा समूहबद्ध गर्न मद्दत गर्न एउटा सानो युटिलिटी स्क्रिप्ट बनाएका थिए, जसलाई FastDom भनिन्छ।

यहाँ Page Builder Sandwich को केही कोड छ जसले FastDom प्रयोग गर्छ र माथिको हाम्रो समाधान #१ पनि समावेश गर्दछ:

// Bunch together the reads from other threads.
fastdom.measure( function() {

	// Use our cached client rect.
	var rect = window.pbsGetBoundingClientRect( elem );

	// Bunch together the writes from other threads.
	fastdom.mutate( function() {
		elem.style.top = ( rect.top + window.pbsScrollY() ) + 'px';
		elem.style.height = rect.height + 'px';
		elem.style.left = rect.left + 'px';
		elem.style.width = rect.width + 'px';
	}.bind( this ) );
}.bind( this ) );​

FastDom ले के गर्छ भने यसले सबै fastdom.measure कलहरू र सबै fastdom.mutate कलहरूलाई समूहबद्ध गर्छ र तिनीहरूलाई अतुल्यकालिक रूपमा (asynchronously) कार्यान्वयन गर्छ, जसले गर्दा ब्राउजरमा सबै रिफ्लोहरू कम गर्न प्रभावकारी रूपमा सबै reads र writes लाई एकै ठाउँमा राख्छ।

समाधान #३: स्टाइल अब्जेक्टहरू (Style Objects) पहुँच गर्नु महँगो हुन्छ

हामीले jsPerf को मद्दतले यो पनि पत्ता लगायौँ कि केही स्टाइल प्राप्त गर्ने प्रकार्यहरू अरूभन्दा छिटो थिए।

PBS मा, हामीलाई सामान्यतया कुनै तत्वको विशिष्ट शैलीको हालको मान प्राप्त गर्न आवश्यक हुन्छ। उदाहरणका लागि, हामी सामान्यतया माथिल्लो मार्जिन (top margin) प्राप्त गर्न यो प्रयोग गर्छौँ:

var styles = window.getComputedStyle( elem );
var marginTop = styles.marginTop;​

तर, हामीले पत्ता लगायौँ कि यो गर्नु धेरै छिटो हुन्छ:

var marginTop = elem.style.marginTop;​

यद्यपि, elem.style.marginTop र getComputedStyle धेरै फरक छन्। पहिलोले हामीलाई केवल तत्वको इनलाइन स्टाइल (inline styling) दिन्छ, र कुनै पनि स्टाइलशिटहरूलाई ध्यानमा राख्दैन; र दोस्रोले इनलाइन स्टाइल र स्टाइलशिट दुवैलाई ध्यानमा राख्छ।

अझ के कुरा छ भने getComputedStyle को नतिजा क्यास गर्दा पनि, यो अझै धेरै ढिलो थियो। पत्ता लागेअनुसार, स्टाइल अब्जेक्ट आफैँ पहुँच गर्ने प्रक्रिया नै ढिलो हुन्छ।

हाम्रो सौभाग्यले, PBS ले तत्वहरूमा इनलाइन स्टाइलहरू थप्छ, र इनलाइन शैलीहरूले स्टाइलशिटहरू भन्दा प्राथमिकता पाउने भएकाले (धेरैजसो समय), यस्ता केसहरू हुनेछन् जहाँ इनलाइन स्टाइलको मान प्राप्त गर्नु पर्याप्त हुनेछ। त्यसैले यदि हामीले माथिका दुई तरिकाहरू संयोजन गर्‍यौँ भने, हामी धेरै छिटो चल्ने कोड बनाउन सक्छौँ:

var marginTop = elem.style.marginTop;
if ( ! marginTop ) {
	var styles = window.getComputedStyle( elem );
	marginTop = styles.marginTop;
}​

कोड सुधारको नतिजा

हाम्रा माथिका ३ वटा समाधानहरूले धेरै अनुसन्धान, परीक्षण र वास्तविक कार्यान्वयन लिए (र हाम्रो फन्ट सुविधा जारी हुन ढिलाइ गर्‍यो), तर अन्तमा यो सबै सार्थक भयो।

स्क्रिनमा २ सेकेन्डका लागि माउस सार्ने हाम्रो प्रारम्भिक परीक्षणमा फर्कँदा, यहाँ पुरानो नराम्रो/अड्किने टाइमलाइन छ:

र पर्फर्मेन्स सुधार पछि हाम्रो नयाँ टाइमलाइन यस्तो देखिन्छ:

तपाईंले देख्न सक्नुहुन्छ, ग्राफमा कम बैजनी सामग्रीहरू छन्। र ब्राउजरद्वारा सञ्चालन भइरहेका प्रक्रियाहरूको मात्रामा उल्लेखनीय भिन्नता छ। तल्लो भागका आयताकार वस्तुहरू पनि पातला छन्, जसको अर्थ प्रक्रियाहरू पहिलेभन्दा छिटो चलिरहेका छन्।

यदि तपाईंले पुराना र नयाँ ग्राफहरूमा अग्ला/फराकिला बैजनी क्षेत्रहरूको मात्रा तुलना गर्नुभयो भने, हामीले उल्लेखनीय रूपमा २००% पर्फर्मेन्स वृद्धि देखिरहेका छौँ! हामी समग्र रूपमा पर्फर्मेन्समा वृद्धि देखिरहेका छौँ। यो पक्कै पनि नराम्रो होइन!

निष्कर्ष र सिकाइहरू

इमानदारीपूर्वक भन्नुपर्दा, एनले कुरा नउठाएसम्म PBS मा पर्फर्मेन्स समस्याहरू छन् भन्ने हामीले सोचेका थिएनौँ। मुख्यतया किनभने हामी विकास-केन्द्रित मेसिनहरू प्रयोग गरिरहेका थियौँ र पर्फर्मेन्स अवरोधहरू त्यति स्पष्ट थिएनन्। पछाडि फर्केर हेर्दा, पर्फर्मेन्स जाँच गर्नु शीर्ष प्राथमिकताहरू मध्ये एक हुनुपर्थ्यो। तर कम्तिमा अहिले, संस्करण ४.३ देखि यसको समाधान गरिएको छ। ?

आफ्नो WordPress प्लगइनको पर्फर्मेन्स सुधार गर्न चाहनेहरूका लागि, विशेष गरी यदि तपाईं भारी जाभास्क्रिप्ट प्रयोग गर्दै हुनुहुन्छ भने, यस अनुभवबाट मेरा निष्कर्षहरू यहाँ छन्:

  1. रिफ्लो ट्रिगर गर्ने गुण/प्रकार्यहरूमा गरिने कलहरूलाई सीमित/क्यास गर्नुहोस्। यसलाई गाइडको रूपमा प्रयोग गर्नुहोस्: What forces layout/reflow. The comprehensive list.
  2. DOM शैलीहरूमा गरिने “reads” र “writes” लाई समूहबद्ध गर्नुहोस्। तपाईंको लागि यो गर्न FastDom प्रयोग गर्नुहोस्।
  3. Chrome को Timeline profiler प्रयोग गर्नुहोस्, र सकेसम्म कम रिफ्लोहरू (बैजनी भागहरू) छन् भन्ने सुनिश्चित गर्नुहोस्।