Прошлог 2. фебруара 2017. године, Ен Тејлор (Ann Taylor) је написала чланак 12 ствари које желимо да видимо код WordPress билдера страница у 2017. + Ко већ ради праву ствар на CodeInWP-у. Већ више од годину дана непрестано унапређујемо Page Builder Sandwich: истражујемо, слушамо наше кориснике, па смо мислили да ће Page Builder Sandwich ући у CodeInWP-ов чланак „Ко то ради како треба“. Ипак, нисмо успели, због перформанси.
Зато смо питали Ен о томе, а она је одговорила:
…Мало сам испробавала бесплатну верзију. Деловала је веома обећавајуће: много модула, прилично интуитиван кориснички интерфејс, сјајно WYSIWYG искуство, све се лако превлачи. Међутим, када сам имала око 10 модула на страници, постало је тешко наставити рад са њом због мале брзине уређивања…
Дакле, према њеним речима, Page Builder Sandwich има проблема са перформансама. А пошто увек слушамо повратне информације, одлучили смо да то истражимо.
Тестирање перформанси
Био нам је потребан начин да проверимо перформансе PBS-а; пошто PBS углавном ради у прегледачу путем Javascript-а, Chrome-ов Timeline профајлер био је прави алат за тај посао. Timeline профајлер може да идентификује и измери који се процеси одвијају на веб-страници током извршавања, што је савршено за проналажење уских грла у брзини.
Сматрали смо да прво треба да тестирамо најједноставније кораке – прелажење мишем преко више елемената и редова. Једноставно, зар не? Били смо уверени да нећемо наићи на проблеме са перформансама код нечег тако једноставног. Нажалост, погрешили смо.
Када смо кликнули на дугме за снимање, померали смо миш око 2 секунде по екрану, а затим притиснули дугме за заустављање. Ево резултујуће временске осе из профајлера:
И гле чуда. Препуно је уских грла у перформансама!
На графикону се дешава много тога, али најочигледнији проблем је што има много љубичастих области (што не би требало да буде случај). Поврх тога, ове љубичасте области су видно веома широке и изгледа да све достижу врх графикона. Такође ћете приметити да је графикон увек пун и да се љубичасте области готово никада не спуштају на нулу. Изгледа као да је прегледач увек заузет радећи нешто и да готово никада не мирује.
Али шта заправо значе све те љубичасте области?
Профајлер је био довољно љубазан да нам каже да се они зову reflows. Ево дефиниције шта је reflow са Google Developer сајта:
Reflow је назив процеса веб-прегледача за поновно израчунавање позиција и геометрије елемената у документу… reflow је операција која блокира корисника у прегледачу.
То значи да померање миша унутар PBS-а покреће гомилу reflow операција. Што је још горе, сваки reflow на тренутак паузира прегледач како би завршио своје прорачуне. Више reflow-а значи више паузирања прегледача, а корисничко искуство постаје још више испрекидано (janky). Једноставније речено, много љубичастих области = лоше.
Тако се испоставило да је Ен била у праву: ми заиста имамо огромно уско грло у перформансама, па се наш тим одмах бацио на посао да то реши.
Решавање нашег 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 нам даје тренутне мере у пикселима за top, left, width, height, right и bottom 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-а
Примена првог решења побољшала је перформансе PBS-а, али то није било довољно.
Гуглајући мало више, наишли смо на чланак који је написао Вилсон Пејџ (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: Приступ објектима стилова је скуп
Уз помоћ jsPerf-а открили смо и да су неке функције за добијање стилова брже од других.
У PBS-у обично треба да добијемо тренутну вредност одређеног стила неког елемента. На пример, обично користимо ово за добијање горње маргине:
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;
}
Резултати побољшања кода
Наша 3 решења изнад захтевала су много истраживања, тестирања и саме примене (и одложила су објављивање наше функције за фонтове), али на крају се све исплатило.
Вратимо се на наш почетни тест померања миша преко екрана током 2 секунде. Ево старе, ружне/испрекидане временске осе:
А ево како изгледа наша нова временска оса након побољшања перформанси:
Као што видите, на графикону има мање љубичастих ствари. И постоји приметна разлика у количини процеса које прегледач обавља. Правоугаоне траке на дну су такође тање, што значи да се процеси извршавају брже него раније.
Ако упоредите количину високих/широких љубичастих области на старом и новом графикону, говоримо о невероватном повећању перформанси од 200%! Такође примећујемо побољшање перформанси у свим сегментима. Уопште није лоше!
Закључци и научене лекције
Да будемо искрени, нисмо мислили да PBS има проблема са перформансама пре него што је Ен то поменула. Углавном зато што смо користили машине намењене развоју, па уска грла у перформансама нису била толико очигледна. Гледајући уназад, провера перформанси је требало да буде један од главних приоритета. Али барем сада, од верзије 4.3, то је решено.
За оне који желе да побољшају перформансе свог WordPress додатка, нарочито ако такође користите обиман Javascript, ево мојих закључака из овог искуства:
- Ограничите/кеширајте своје позиве својствима/функцијама које покрећу reflow. Користите ово као водич: Шта форсира распоред/reflow. Свеобухватна листа.
- Групишите „читања“ и „уписивања“ стилова DOM-а. Користите FastDom да то уради за вас.
- Користите Chrome-ов Timeline профајлер и побрините се да имате што је могуће мање reflow-а (љубичастих делова).