2017. február 2-án Ann Taylor megírta a 12 Things We Need to See From WordPress Page Builders in 2017 + Who Already Gets It Right című cikket a CodeInWP-n. Már több mint egy éve folyamatosan fejlesztjük a Page Builder Sandwichet: kutatunk, figyelünk az ügyfeleinkre, így azt gondoltuk, hogy a Page Builder Sandwich bekerül a CodeInWP „Who Got it Right” cikkébe. A teljesítmény miatt azonban lemaradtunk róla.
Így hát megkérdeztük erről Annt, és ő ezt válaszolta:
…Kipróbáltam egy kicsit az ingyenes verziót. Nagyon ígéretes: rengeteg modul, meglehetősen intuitív kezelőfelület, nagyszerű WYSIWYG élmény, mindent könnyű mozgatni. Amikor azonban már körülbelül 10 modul volt az oldalon, az alacsony szerkesztési sebesség miatt nehézkessé vált a munka folytatása…
Szóval szerinte a Page Builder Sandwichnek teljesítményproblémái vannak. És mivel mindig odafigyelünk a visszajelzésekre, utánajártunk a dolognak.
A teljesítmény tesztelése
Szükségünk volt egy módszerre a PBS teljesítményének ellenőrzésére; mivel a PBS többnyire a böngészőben fut JavaScripten keresztül, a Chrome Timeline profilozója volt a legmegfelelőbb eszköz erre a feladatra. Az idővonal-profilozó képes azonosítani és megmérni, hogy milyen folyamatok zajlanak egy weboldalon futás közben, ami tökéletes a sebességbeli szűk keresztmetszetek felderítésére.
Úgy gondoltuk, hogy először a legegyszerűbb lépéseket kellene tesztelnünk – végighúzni az egeret több elemen és soron. Egyszerű, nemde? Biztosak voltunk benne, hogy egy ilyen egyszerű dologgal nem ütközünk teljesítményproblémákba. Sajnos tévedtünk.
Miután rákattintottunk a felvétel gombra, körülbelül 2 másodpercig mozgattuk az egeret a képernyőn, majd megnyomtuk a leállítás gombot. Íme az eredményül kapott idővonal a profilozóból:
És láss csodát: tele van teljesítménybeli szűk keresztmetszetekkel!
Rengeteg dolog történik a grafikonon, de a legnyilvánvalóbatlanabb probléma az, hogy tele van lila területekkel (aminek nem kellene így lennie). Ráadásul ezek a lila területek láthatóan rendkívül szélesek, és úgy tűnik, mindegyik eléri a grafikon csúcsát. Azt is észreveheti, hogy a grafikon szinte mindig teljesen tele van, és a lila területek szinte sosem csökkennek nullára. Úgy tűnik, a böngésző mindig el van foglalva valamivel, és szinte sosem pihen.
De mit is jelentenek valójában ezek a lila területek?
A profilozó volt olyan kedves, és elárulta nekünk, hogy ezeket reflow-knak (újratördeléseknek) hívják. Íme a reflow definíciója a Google Fejlesztői oldaláról:
A reflow annak a webböngésző-folyamatnak a neve, amely újraszámolja a dokumentumban lévő elemek pozícióját és geometriáját.. a reflow a felhasználói műveleteket blokkoló folyamat a böngészőben.
Ez tehát azt jelenti, hogy az egér mozgatása a PBS-ben egy rakás reflow-t vált ki. Ami még rosszabb, hogy minden egyes reflow pillanatokra megakasztja a böngészőt, hogy befejezhesse a számításait. A több reflow több böngésző-megakadást jelent, az élmény pedig még szaggatottabbá válik. Egyszerűbben fogalmazva: a sok lila terület = rossz.
Kiderült tehát, hogy Ann-nek igaza volt: valóban hatalmas teljesítménybeli szűk keresztmetszettel rendelkezünk, így csapatunk azonnal munkához látott, hogy kijavítsa a hibát.
A reflow / szaggatási probléma megoldása
Mindenekelőtt kutatást kellett végeznünk, hogy kiderítsük, mi okozza ezt a reflow-t egyáltalán. Rájöttünk, hogy láthatóan számos gyakori JavaScript-művelet késztetheti a böngészőt reflow-ra.
Szinte bármelyik függvény, amellyel egy DOM-elem szélességét, magasságát vagy pozícióját kérdezzük le, reflow-t indíthat el. Paul Irish egy részletes listát írt az összes olyan JavaScript-tulajdonságról és függvényről, amely ezt eredményezheti. A rossz hír az, hogy a Page Builder Sandwich rengeteget használ ezekből a tulajdonságokból és függvényekből, és ezek mindegyike reflow-t okoz:
- elem.getBoundingClientRect()
- window.getComputedStyle()
- window.scrollY
- és még sok más…
1. javítás: A költséges reflow-folyamatok gyorsítótárazása
A legkézenfekvőbb megoldás az lenne, ha NEM használnánk ezeket a tulajdonságokat és függvényeket. A PBS azonban nagymértékben támaszkodik rájuk, és nem tudunk nélkülük működni.
Az egyetlen megmaradt megoldásunk az volt, hogy a lehető legjobban lecsökkentsük az ezekre a függvényekre irányuló hívások számát. A nehézséget az okozta, hogy ezeket a tulajdonságokat és függvényeket nagyon sokszor használtuk a PBS különböző részein, és legtöbbször ugyanazon a DOM-elemen többször is. Ennek orvoslására létrehoztuk a saját gyorsítótárazási mechanizmusunkat minden problematikus tulajdonsághoz/függvényhez.
Például a getBoundingClientRect függvény megadja egy DOM-elem aktuális felső, bal oldali, szélességi, magassági, jobb oldali és alsó képpontméreteit. Ezt a függvényt folyamatosan használjuk annak a pozíciónak a kiszámításához, ahová az elemek körvonalait rajzoljuk a PBS-ben. Íme az erre létrehozott gyorsítótárazó függvényünk:
// Gyorsítótár: Itt tároljuk a client rect-eket, ezt használhatjuk a későbbi törlésükhöz.
var elemsWithBoundingRects = [];
window.pbsGetBoundingClientRect = function( element ) {
// Ellenőrizzük, hogy korábban lekértük-e már a client rectet.
if ( ! element._boundingClientRect ) {
// Ha nem, lekérjük, majd elmentjük későbbi használatra.
element._boundingClientRect = element.getBoundingClientRect();
elemsWithBoundingRects.push( element );
}
return element._boundingClientRect;
};
A getBoundingClientRect közvetlen meghívása helyett mostantól a pbsGetBoundingClientRect függvényt használtuk. Így minden alkalommal, amikor a client rect lekérdezésre kerül, eltároljuk ezt az értéket későbbi hivatkozás céljából, ezáltal korlátozva a reflow-k kiváltásának számát.
Szükségünk volt egy módszerre a gyorsítótár ürítésére is, amikor a tárolt client rect-ek érvénytelenné válnak. A tárolt client rect értékei megváltoznak, amikor a felhasználó görgeti az oldalt, vagy elemeket ad hozzá/módosít/távolít el a képernyőn. Ezért létrehoztunk egy gyorsítótár-törlő függvényt, amelyet bármikor meghívhatunk, ha friss client rect értékekre van szükségünk:
var clearClientRects = function() {
var i;
for ( i = 0; i < elemsWithBoundingRects.length; i++ ) {
if ( elemsWithBoundingRects[ i ] ) {
elemsWithBoundingRects[ i ]._boundingClientRect = null;
}
}
elemsWithBoundingRects = [];
};
Ugyanezt a gyorsítótárazási mechanizmust alkalmaztuk a többi gyakran használt, költséges függvényhívásunkra is.
2. javítás: Az olvasások és írások csoportosítása a FastDom segítségével
Az 1. javítás bevezetése növelte a PBS teljesítményét, de még nem volt elegendő.
Tovább keresgélve a Google-ben rábukkantunk Wilson Page cikkére a layout thrashing megelőzéséről. Wilson ezt írja:
A Layout Thrashing (reflow) akkor következik be, amikor a JavaScript drasztikusan ír a DOM-ba, majd olvas belőle egymás után többször is, ami dokumentum-reflow-kat okoz.
Úgy tűnik, nem elég csupán korlátozni a konkrét reflow-t kiváltó tulajdonságok és függvények meghívását. Amikor egymás után több „olvasást” és „írást” hajt végre a DOM-on, az szintén böngésző-reflow-t vált ki. Íme egy tipikus forgatókönyv, közvetlenül Wilson oldaláról:
// Olvasás var h1 = element1.clientHeight; // Írás (érvényteleníti az elrendezést) element1.style.height = (h1 * 2) + 'px'; // Olvasás (reflow-t vált ki) var h2 = element2.clientHeight; // Írás (érvényteleníti az elrendezést) element2.style.height = (h2 * 2) + 'px'; // Olvasás (reflow-t vált ki) var h3 = element3.clientHeight; // Írás (érvényteleníti az elrendezést) element3.style.height = (h3 * 2) + 'px'; // Dokumentum-reflow a képkocka végén
A fenti példa 3 reflow-t vált ki, mivel a teljes oldal méretei érvénytelenné válnak egy írás után. Erre a javasolt javítás a kód átrendezése úgy, hogy egy csoportba vonjuk az összes olvasást és írást, így az egész folyamat mindössze 1 böngésző-reflow-t indít el:
// Olvasás var h1 = element1.clientHeight; var h2 = element2.clientHeight; var h3 = element3.clientHeight; // Írás (érvényteleníti az elrendezést) element1.style.height = (h1 * 2) + 'px'; element2.style.height = (h2 * 2) + 'px'; element3.style.height = (h3 * 2) + 'px'; // Dokumentum-reflow a képkocka végén
A fenti kódrészlet alapján a probléma megoldása egyszerűnek tűnhet. A valóságban azonban a PBS-ben a reflow-t kiváltó adatok olvasása és írása rengeteg különböző helyen és a PBS életciklusa során számos különböző időpontban történik. Több ezer sornyi JavaScript-kód átrendezése nem volt kivitelezhető.
Szerencsénkre Wilson megoldást is javasolt a cikkében. Készített egy kis segédszkriptet az olvasások és írások csoportosításának megkönnyítésére, amely a FastDom nevet viseli.
Íme egy kódrészlet a Page Builder Sandwichből, amely a FastDomot használja, és magában foglalja a fenti 1. javításunkat is:
// Összegyűjtjük a más szálakból származó olvasásokat.
fastdom.measure( function() {
// A gyorsítótárazott client rect használata.
var rect = window.pbsGetBoundingClientRect( elem );
// Összegyűjtjük a más szálakból származó írásokat.
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 ) );
A FastDom lényege, hogy egybefogja az összes fastdom.measure és fastdom.mutate hívást, és aszinkron módon hajtja végre őket, hatékonyan csoportosítva az összes olvasást és írást a böngészőbeli reflow-k minimalizálása érdekében.
3. javítás: A stílusobjektumok elérése költséges
A jsPerf segítségével arra is rájöttünk, hogy bizonyos stíluslekérdező függvények gyorsabbak másoknál.
A PBS-ben gyakran le kell kérnünk egy elem adott stílusának aktuális értékét. Például a felső margó lekéréséhez általában ezt használtuk:
var styles = window.getComputedStyle( elem ); var marginTop = styles.marginTop;
Azonban rájöttünk, hogy ez a megoldás sokkal gyorsabb:
var marginTop = elem.style.marginTop;
Azonban az elem.style.marginTop és a getComputedStyle nagyon eltérő módon működik. Az első csak az elem soron belüli (inline) stílusát adja vissza, és nem veszi figyelembe a stíluslapokat; a második viszont mind az inline stílusokat, mind a stíluslapokat figyelembe veszi.
Ráadásul még akkor is, amikor a getComputedStyle eredménye gyorsítótárazva volt, még mindig rendkívül lassúnak bizonyult. Mint kiderült, maga a stílusobjektum elérése a lassú folyamat.
Szerencsénkre a PBS inline stílusokat rendel az elemekhez, és mivel az inline stílusok (az esetek túlnyomó részében) elsőbbséget élveznek a stíluslapokkal szemben, lesznek olyan esetek, amikor az inline stílus értékének lekérése is bőven elegendő. Így ha kombináljuk a fenti két módszert, egy sokkal gyorsabban lefutó kódot hozhatunk létre:
var marginTop = elem.style.marginTop;
if ( ! marginTop ) {
var styles = window.getComputedStyle( elem );
marginTop = styles.marginTop;
}
A kódfejlesztések eredményei
A fenti 3 javításunk rengeteg kutatást, tesztelést és tényleges megvalósítást igényelt (és késleltette a Fonts [Betűtípusok] funkciónk megjelenését), de a végén minden erőfeszítés megérte.
Visszatérve a kezdeti tesztünkhöz, amely során 2 másodpercig mozgattuk az egeret a képernyőn: Íme a régi, csúnya/szaggató idővonal:
És így néz ki az új idővonalunk a teljesítményjavítások után:
Amint látható, jóval kevesebb lila sáv szerepel a grafikonon. És észrevehető a különbség a böngésző által végrehajtott folyamatok mennyiségében is. Az alsó téglalapok is vékonyabbak lettek, ami azt jelenti, hogy a folyamatok gyorsabban futnak le, mint korábban.
Ha összehasonlítjuk a magas/széles lila területek mennyiségét a régi és az új grafikonokon, elképesztő, 200%-os teljesítménynövekedésről beszélhetünk! Emellett általános teljesítményjavulást tapasztalunk minden területen. Egyáltalán nem rossz!
Tanulságok és tapasztalatok
Őszintén szólva nem gondoltuk, hogy a PBS-nek teljesítményproblémái lennének, mielőtt Ann fel nem hívta rá a figyelmet. Főként azért, mert fejlesztésre szánt, erős gépeket használtunk, és a szűk keresztmetszetek nem voltak annyira nyilvánvalóak. Visszatekintve, a teljesítmény ellenőrzésének az egyik legfőbb prioritásnak kellett volna lennie. De legalább most, a 4.3-as verziótól kezdve ezt sikeresen megoldottuk.
Azok számára, akik szeretnék növelni saját WordPress-bővítményük teljesítményét – különösen, ha sok JavaScriptet használnak –, íme a tanulságaim ebből a tapasztalatból:
- Korlátozza/gyorsítótárazza a reflow-t kiváltó tulajdonságok/függvények hívását! Használja ezt útmutatóként: What forces layout/reflow. The comprehensive list.
- Csoportosítsa össze a DOM-stílusok „olvasásait” és „írásait”! Használja a FastDom segédprogramot erre a feladatra!
- Használja a Chrome Timeline profilozóját, és gondoskodjon arról, hogy a lehető legkevesebb reflow (lila elem) keletkezzen!