Ինչպես մենք բարձրացրինք մեր փլագինի արտադրողականությունը 200%-ով

Հեղինակ՝

բաժնում՝

,

Անցյալ 2017 թվականի փետրվարի 2-ին Էնն Թեյլորը (Ann Taylor) 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-ի «Ովքեր են դա ճիշտ արել» հոդվածում: Սակայն չհայտնվեց արտադրողականության պատճառով: ?

Ուստի մենք հարցրինք Էննին այդ մասին, և նա պատասխանեց.

…Ես մի փոքր փորձարկեցի անվճար տարբերակը: Այն շատ խոստումնալից է. բազմաթիվ մոդուլներ, բավականին ինտուիտիվ միջերես, հիանալի WYSIWYG փորձառություն, ամեն ինչ հեշտությամբ է տեղաշարժվում: Այնուամենայնիվ, երբ էջում ունեցա մոտ 10 մոդուլ, խմբագրման ցածր արագության պատճառով դժվարացավ դրանով աշխատանքը շարունակելը…

Այսպիսով, նրա խոսքով, Page Builder Sandwich-ն ունի արտադրողականության խնդիրներ: Եվ քանի որ մենք միշտ լսում ենք կարծիքները, մենք ստուգեցինք դա:

Արտադրողականության թեստավորում

Մեզ անհրաժեշտ էր PBS-ի արտադրողականությունը ստուգելու եղանակ. քանի որ PBS-ը հիմնականում աշխատում է բրաուզերում Javascript-ի միջոցով, Chrome-ի Timeline պրոֆայլերը ճիշտ գործիք էր այդ աշխատանքի համար: Timeline պրոֆայլերը կարող է նույնականացնել և չափել, թե ինչ գործընթացներ են տեղի ունենում վեբ էջում աշխատանքի ընթացքում, ինչը հիանալի է արագության խոչընդոտները գտնելու համար:

Մենք որոշեցինք, որ նախ պետք է փորձարկենք ամենապարզ քայլերը՝ մկնիկը շարժելով բազմաթիվ տարրերի և տողերի վրայով: Պարզ է, չէ՞: Մենք վստահ էինք, որ այսպիսի պարզ բանի հետ կապված արտադրողականության խնդիրներ չենք ունենա: Ցավոք, սխալվեցինք:

Հենց սեղմեցինք ձայնագրման կոճակը, մոտ 2 վայրկյան մկնիկը շարժեցինք էկրանի վրայով, ապա սեղմեցինք կանգնեցնելու կոճակը:  Ահա պրոֆայլերից ստացված ժամանակային գրաֆիկը.

Եվ ահա, տեսեք: Այն լի է արտադրողականության խոչընդոտներով:

Գրաֆիկում շատ բան է կատարվում, բայց ամենաակնհայտ խնդիրն այն է, որ դրանում շատ են մանուշակագույն հատվածները (ինչը չպետք է լիներ): Բացի այդ, այս մանուշակագույն հատվածները տեսանելիորեն շատ լայն են, և բոլորն էլ, կարծես, հասնում են գրաֆիկի գագաթնակետին: Կնկատեք նաև, որ գրաֆիկը միշտ լիքն է, և մանուշակագույն հատվածները գրեթե երբեք չեն իջնում զրոյի: Կարծես բրաուզերը միշտ ինչ-որ բանով զբաղված է և գրեթե երբեք չի հանգստանում:

Բայց ի՞նչ են իրականում նշանակում այդ բոլոր մանուշակագույն հատվածները:

Պրոֆայլերը բարեհաճորեն հուշեց մեզ, որ դրանք կոչվում են reflow-ներ: Ահա, թե ինչ է reflow-ն՝ ըստ Google Developer կայքի:

Reflow-ն վեբ բրաուզերի գործընթացի անվանումն է, որը վերահաշվարկում է փաստաթղթի տարրերի դիրքերն ու երկրաչափությունը… reflow-ն բրաուզերում օգտատիրոջ գործողությունները արգելափակող գործողություն է:

Այսպիսով, դա նշանակում է, որ PBS-ում մկնիկը շարժելը հարուցում է հսկայական քանակությամբ reflow-ներ: Ավելի վատն այն է, որ յուրաքանչյուր reflow ակնթարթորեն կանգնեցնում է բրաուզերը, որպեսզի այն կարողանա ավարտել իր հաշվարկները: Ավելի շատ reflow-ները նշանակում են բրաուզերի ավելի շատ դադարներ, իսկ օգտատիրոջ փորձառությունը դառնում է էլ ավելի դանդաղաշարժ ու ընդհատումներով: Ավելի պարզ ասած՝ շատ մանուշակագույն հատվածներ = վատ է:

Այսպիսով, պարզվեց, որ Էննը ճիշտ էր. մենք իսկապես ունենք արտադրողականության լուրջ խոչընդոտ, ուստի մեր թիմն անմիջապես գործի անցավ այն շտկելու համար:

Մեր Reflow / Jank խնդրի շտկումը

Ամեն ինչից առաջ մենք պետք է հետազոտություն կատարեինք՝ պարզելու, թե հատկապես ինչն է առաջին հերթին առաջացնում այս reflow-ն: Մենք պարզեցինք, որ, ըստ երևույթին, Javascript-ում շատ տարածված բաներ կարող են դրդել բրաուզերին reflow առաջացնել:

Գրեթե ցանկացած ֆունկցիա, որը կարող եք օգտագործել DOM տարրի լայնությունը, բարձրությունը կամ դիրքը ստանալու համար, կարող է reflow հարուցել: Փոլ Այրիշը (Paul Irish) գրել է ընդարձակ ցանկ Javascript-ի բոլոր այն հատկությունների և ֆունկցիաների, որոնք կարող են դա անել: Վատ նորությունն այն է, որ Page Builder Sandwich-ն օգտագործում է այդ հատկություններից և ֆունկցիաներից շատերը, որոնցից յուրաքանչյուրը reflow է առաջացնում.

  • elem.getBoundingClientRect()
  • window.getComputedStyle()
  • window.scrollY
  • և շատ ավելին…

Լուծում #1. Ռեսուրսատար Reflow գործընթացների քեշավորում

Ամենաակնհայտ լուծումն այդ հատկություններն ու ֆունկցիաները Չօգտագործելն է: Այնուամենայնիվ, PBS-ը մեծապես հենվում է դրանց վրա, և մենք չենք կարող յոլա գնալ առանց դրանց:

Մեզ մնացած լուծումն այդ ֆունկցիաների կանչերը հնարավորինս կրճատելն էր: Դժվարն այն էր, որ մենք այդ հատկություններն ու ֆունկցիաները շատ անգամներ էինք օգտագործել PBS-ի տարբեր մասերում, և մեծ մասամբ դրանք բազմիցս օգտագործել էինք միևնույն DOM տարրի վրա: Սա շտկելու համար մենք ստեղծեցինք մեր սեփական քեշավորման մեխանիզմը յուրաքանչյուր խնդրահարույց հատկության/ֆունկցիայի համար:

Օրինակ՝ getBoundingClientRect  ֆունկցիան տալիս է DOM տարրի վերևի, ձախ, լայնության, բարձրության, աջ և ներքևի պիքսելային չափումները: Մենք անընդհատ օգտագործում ենք այս ֆունկցիան՝ PBS-ում տարրերի ուրվագծերը նկարելու տեղը հաշվարկելու համար: Ահա քեշավորման ֆունկցիան, որը մենք ստեղծել ենք դրա համար.

// Քեշ. Մեր client rect-երը պահվում են այստեղ, մենք կարող ենք օգտագործել սա դրանք հետագայում մաքրելու համար:
var elemsWithBoundingRects = [];

window.pbsGetBoundingClientRect = function( element ) {

	// Ստուգել, արդյոք նախկինում արդեն ստացել ենք client rect-ը:
	if ( ! element._boundingClientRect ) {

		// Եթե ոչ, ստանալ այն, ապա պահել հետագա օգտագործման համար:
		element._boundingClientRect = element.getBoundingClientRect();
		elemsWithBoundingRects.push( element );
	}
	return element._boundingClientRect;
};
​

getBoundingClientRect  կանչելու փոխարեն մենք այժմ օգտագործեցինք pbsGetBoundingClientRect : Այժմ ամեն անգամ, երբ հարցվում է client rect-ը, մենք պահպանում ենք այդ արժեքը հետագա օգտագործման համար՝ դրանով իսկ սահմանափակելով reflow հարուցիչների քանակը:

Մեզ նաև անհրաժեշտ էր քեշը մաքրելու եղանակ, երբ պահպանված client rect-երը դառնում են անվավեր: Պահպանված client rect-ի արժեքները փոխվում են, երբ օգտատերը թերթում է էջը կամ էկրանին ավելացնում/փոփոխում/հեռացնում է տարրեր: Ուստի մենք ստեղծեցինք քեշի մաքրման ֆունկցիա, որը կարող ենք կանչել ամեն անգամ, երբ մեզ անհրաժեշտ են client rect-ի թարմ արժեքներ.

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

Մենք նույն քեշավորման մեխանիզմը կիրառեցինք նաև այլ ռեսուրսատար ֆունկցիաների կանչերի համար, որոնք սովորաբար օգտագործում ենք:

Լուծում #2. Ընթերցումների և գրառումների խմբավորում FastDom-ի միջոցով

Լուծում #1-ի ներդրումը բարձրացրեց PBS-ի արտադրողականությունը, բայց դա բավարար չէր:

Մի փոքր ավելի շատ որոնելով Google-ում՝ մենք հանդիպեցինք Ուիլսոն Փեյջի (Wilson Page) «Շարվածքի ծանրաբեռնվածության (layout thrashing) կանխարգելում» հոդվածին: Ուիլսոնն ասում է.

Layout Thrashing-ը (reflow) տեղի է ունենում, երբ JavaScript-ը բռնի կերպով բազմիցս գրում, ապա կարդում է DOM-ից՝ առաջացնելով փաստաթղթի reflow-ներ:

Ըստ երևույթին, բավարար չէ միայն սահմանափակել reflow հարուցող կոնկրետ հատկությունների և ֆունկցիաների կանչերը: Երբ DOM-ում կատարում եք մի քանի հաջորդական «ընթերցումներ» և «գրառումներ», դա նույնպես հարուցում է բրաուզերի reflow: Ահա տիպիկ սցենար՝ ուղիղ Ուիլսոնի կայքից.

// Ընթերցում
var h1 = element1.clientHeight;

// Գրառում (անվավերացնում է շարվածքը)
element1.style.height = (h1 * 2) + 'px';

// Ընթերցում (հարուցում է reflow)
var h2 = element2.clientHeight;

// Գրառում (անվավերացնում է շարվածքը)
element2.style.height = (h2 * 2) + 'px';

// Ընթերցում (հարուցում է reflow)
var h3 = element3.clientHeight;

// Գրառում (անվավերացնում է շարվածքը)
element3.style.height = (h3 * 2) + 'px';

// Փաստաթղթի reflow կադրի վերջում​

Վերոնշյալ օրինակը հարուցում է 3 reflow, քանի որ գրառումից հետո ամբողջ էջի չափսերն անվավեր են դառնում: Դրա համար առաջարկվող լուծումը կոդը վերադասավորելն է, որպեսզի բոլոր ընթերցումներն ու գրառումները խմբավորվեն միասին, այնպես որ ամբողջ գործընթացը հարուցի ընդամենը 1 բրաուզերային reflow.

// Ընթերցում
var h1 = element1.clientHeight;
var h2 = element2.clientHeight;
var h3 = element3.clientHeight;

// Գրառում (անվավերացնում է շարվածքը)
element1.style.height = (h1 * 2) + 'px';
element2.style.height = (h2 * 2) + 'px';
element3.style.height = (h3 * 2) + 'px';

// Փաստաթղթի reflow կադրի վերջում​

Վերևի կոդի հատվածը խնդրի լուծումը հեշտ է ցույց տալիս: Բայց իրականում, PBS-ում reflow առաջացնող տարրերի ընթերցումն ու գրառումը տեղի են ունենում բազմաթիվ տարբեր տեղերում և շատ տարբեր ժամանակներում՝ PBS-ի կենսացիկլի ընթացքում: Javascript կոդի հազարավոր տողերի վերակազմակերպումն իրատեսական չէ:

Բարեբախտաբար, Ուիլսոնն իր հոդվածում տրամադրել է նաև լուծում: Նա ստեղծել է փոքր օժանդակ սկրիպտ, որը կոչվում է FastDom և օգնում է խմբավորել այդ ընթերցումներն ու գրառումները:

Ահա Page Builder Sandwich-ից կոդի մի հատված, որն օգտագործում է FastDom-ը և ներառում է նաև մեր վերը նշված Լուծում #1-ը.

// Միավորել այլ հոսքերից ընթերցումները:
fastdom.measure( function() {

	// Օգտագործել մեր քեշավորված client rect-ը:
	var rect = window.pbsGetBoundingClientRect( elem );

	// Միավորել այլ հոսքերից գրառումները:
	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 -ի բոլոր կանչերը ու դրանք կատարում ասինխրոն կերպով՝ արդյունավետորեն խմբավորելով բոլոր ընթերցումներն ու գրառումները՝ բրաուզերում reflow-ների քանակը նվազեցնելու համար:

Լուծում #3. Ոճերի (Style) օբյեկտներին դիմելը ռեսուրսատար է

Մենք նաև jsPerf-ի օգնությամբ պարզեցինք, որ ոճը ստանալու որոշ ֆունկցիաներ ավելի արագ են, քան մյուսները:

PBS-ում մեզ սովորաբար անհրաժեշտ է լինում ստանալ տարրի որոշակի ոճի ընթացիկ արժեքը: Օրինակ՝ վերևի լուսանցքը ստանալու համար մենք սովորաբար օգտագործում ենք սա.

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

Բայց մենք պարզեցինք, որ սա անելն անհամեմատ ավելի արագ է.

var marginTop = elem.style.marginTop;​

Այնուամենայնիվ, elem.style.marginTop -ը և getComputedStyle -ը շատ տարբեր են: Առաջինը մեզ տալիս է միայն տարրի ներկառուցված (inline) ոճավորումը և հաշվի չի առնում որևէ ոճաթերթ. իսկ երկրորդը հաշվի է առնում ինչպես inline ոճերը, այնպես էլ ոճաթերթերը:

Ավելին, նույնիսկ երբ getComputedStyle -ի արդյունքը քեշավորված էր, այն միևնույն է շատ դանդաղ էր: Ինչպես պարզվեց, հենց ոճի օբյեկտին դիմելն է դանդաղ գործընթացը:

Բարեբախտաբար, PBS-ը տարրերին կցում է inline ոճեր, և քանի որ inline ոճերը (մեծ մասամբ) գերակայություն ունեն ոճաթերթերի նկատմամբ, կլինեն դեպքեր, երբ inline ոճի արժեքը ստանալը լիովին բավարար կլինի: Այսպիսով, եթե միավորենք վերը նշված երկու մեթոդները, կարող ենք ստանալ շատ ավելի արագ աշխատող կոդ.

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

Կոդի բարելավումների արդյունքները

Վերոնշյալ մեր 3 շտկումները պահանջեցին շատ հետազոտություններ, թեստավորում և փաստացի ներդրում (և հետաձգեցին մեր Fonts ֆունկցիայի թողարկումը), բայց ի վերջո այդ ամենն արժեր դրան:

Վերադառնալով մեր նախնական թեստին՝ մկնիկը 2 վայրկյան էկրանով շարժելուն: Ահա հին տգեղ/դանդաղաշարժ ժամանակային գրաֆիկը.

Եվ ահա, թե ինչպիսի տեսք ունի մեր նոր ժամանակային գրաֆիկը արտադրողականության բարելավումներից հետո.

Ինչպես տեսնում եք, գրաֆիկում մանուշակագույն հատվածներն ավելի քիչ են: Եվ նկատելի տարբերություն կա բրաուզերի կողմից իրականացվող գործընթացների քանակի մեջ: Ներքևի ուղղանկյուն հատվածները նույնպես ավելի բարակ են, ինչը նշանակում է, որ գործընթացներն ավելի արագ են կատարվում, քան նախկինում:

Եթե համեմատեք հին և նոր գրաֆիկներում բարձր/լայն մանուշակագույն հատվածների քանակը, ապա կտեսնեք ապշեցուցիչ՝ արտադրողականության 200% աճ: Մենք տեսնում ենք նաև արտադրողականության համատարած աճ: Ամենևին վատ չէ:

Հիմնական եզրահանգումներն ու դասերը

Անկեղծ ասած, մենք չէինք կարծում, որ PBS-ն ունի արտադրողականության խնդիրներ, քանի դեռ Էննը չէր բարձրաձայնել դրա մասին: Հիմնականում այն պատճառով, որ մենք օգտագործում էինք մշակողների համար նախատեսված համակարգիչներ, և արտադրողականության խոչընդոտներն այնքան էլ ակնհայտ չէին: Հետադարձ հայացք գցելով՝ արտադրողականության ստուգումը պետք է լիներ գլխավոր առաջնահերթություններից մեկը: Բայց գոնե հիմա՝ սկսած 4.3 տարբերակից, այդ հարցը լուծված է: ?

Նրանց համար, ովքեր ցանկանում են բարելավել իրենց WordPress փլագինի արտադրողականությունը, հատկապես եթե դուք նույնպես օգտագործում եք ծավալուն Javascript, ահա այս փորձից քաղված իմ եզրահանգումները.

  1. Սահմանափակեք/քեշավորեք ձեր կանչերը դեպի reflow առաջացնող հատկություններ/ֆունկցիաներ: Որպես ուղեցույց օգտագործեք սա. What forces layout/reflow. The comprehensive list.
  2. Խմբավորեք DOM ոճերի «ընթերցումները» և «գրառումները»: Դրա համար օգտագործեք FastDom-ը:
  3. Օգտագործեք Chrome-ի Timeline պրոֆայլերը և համոզվեք, որ ունեք հնարավորինս քիչ reflow-ներ (մանուշակագույն հատվածներ):