Plaqinimizin performansını 200% necə artırdıq

Müəllif:

bölmə:

,

Ötən 2 fevral 2017-ci il tarixində Ann Taylor CodeInWP-də 12 Things We Need to See From WordPress Page Builders in 2017 + Who Already Gets It Right adlı məqalə yazdı. Biz artıq bir ildən çoxdur ki, Page Builder Sandwich-i davamlı olaraq təkmilləşdiririk: araşdırmalar aparırıq, müştərilərimizi dinləyirik, ona görə də Page Builder Sandwich-in CodeInWP-nin “Who Got it Right” məqaləsinə düşəcəyini düşünürdük. Lakin performans səbəbindən düşə bilmədik. ?

Ona görə də bu barədə Ann-dən soruşduq və o belə cavab verdi:

…Pulsuz versiya ilə bir az işlədim. Çox ümidvericidir: çoxlu modullar, kifayət qədər intuitiv UI, əla WYSIWYG təcrübəsi, hər şey asanlıqla sürüklənir. Lakin səhifədə təxminən 10 modul olanda, aşağı redaktə sürətinə görə onunla işləməyə davam etmək çətinləşdi…

Beləliklə, onun dediyinə görə, Page Builder Sandwich-in performans problemləri var. Biz də həmişə rəylərə qulaq asdığımız üçün bunu yoxladıq.

Performansın Test Edilməsi

PBS-in performansını yoxlamaq üçün bir üsul lazım idi; PBS əsasən Javascript vasitəsilə brauzerdə işlədiyindən, bu iş üçün düzgün alət Chrome-un Timeline profiler aləti idi. Timeline profiler veb-səhifənin işlədiyi zaman hansı proseslərin baş verdiyini müəyyən edə və ölçə bilir ki, bu da sürət darboğazlarını tapmaq üçün əladır.

İlk olaraq ən sadə addımları yoxlamaq qərarına gəldik – kursoru bir neçə element və sətir üzərində gəzdirmək. Sadədir, elə deyilmi? Bu qədər sadə bir şeylə performans problemləri ilə qarşılaşmayacağımıza əmin idik. Təəssüf ki, yanılmışdıq.

Yazma düyməsini basdıqdan sonra siçanı ekranda təxminən 2 saniyə gəzdirdik, sonra dayandırma düyməsini basdıq. Profilerdən əldə olunan nəticə qrafiki (timeline) budur:

Budur! Tamamilə performans darboğazları ilə doludur!

Qrafikdə çox şey baş verir, lakin ən bariz problem oradakı bənövşəyi sahələrin çoxluğudur (hansı ki, belə olmamalıdır). Üstəlik, bu bənövşəyi sahələr gözlə görünəcək dərəcədə çox genişdir və hamısı sanki qrafikin zirvəsinə çatır. Həmçinin görəcəksiniz ki, qrafik həmişə doludur və bənövşəyi sahələr demək olar ki, heç vaxt sıfıra enmir. Görünən odur ki, brauzer həmişə nə iləsə məşğuldur və demək olar ki, heç vaxt dincəlmir.

Bəs bütün bu bənövşəyi sahələr əslində nə deməkdir?

Profiler sağ olsun, bizə bunların reflows adlandığını bildirdi. Google Developer saytından:

Reflow — sənəddəki elementlərin mövqelərinin və həndəsi ölçülərinin yenidən hesablanması üzrə veb-brauzer prosesinin adıdır… reflow brauzerdə istifadəçinin fəaliyyətini bloklayan bir əməliyyatdır.

Deməli, bu o deməkdir ki, siçanı PBS daxilində gəzdirmək saysız-hesabsız reflow-a səbəb olur. Daha pisi odur ki, hər bir reflow hesablamalarını başa çatdıra bilmək üçün brauzeri anlıq olaraq dayandırır. Daha çox reflow daha çox brauzer fasiləsi deməkdir və istifadə təcrübəsi daha da donur (janky). Sadə dillə desək, çoxlu bənövşəyi sahə = pis.

Belə çıxır ki, Ann haqlı idi: bizim həqiqətən də böyük performans darboğazımız var, ona görə də komandamız bunu dərhal düzəltmək üçün işə başladı.

Reflow / Donma Problemimizin Həlli

Hər şeydən əvvəl, ilk növbədə bu reflow-a nəyin səbəb olduğunu öyrənmək üçün bəzi araşdırmalar aparmalı idik. Məlum oldu ki, görünür, Javascript-dəki bir çox ümumi şey brauzeri reflow etməyə vadar edə bilər.

DOM elementinin enini, hündürlüyünü və ya mövqeyini əldə etmək üçün istifadə edə biləcəyiniz demək olar ki, hər hansı funksiya reflow-a səbəb ola bilər. Paul Irish Javascript-də buna səbəb ola biləcək bütün xassələrin və funksiyaların geniş siyahısını yazmışdır. Pis xəbər odur ki, Page Builder Sandwich hamısı reflow-a səbəb olan bu xassələrin və funksiyaların çoxundan istifadə edir:

  • elem.getBoundingClientRect()
  • window.getComputedStyle()
  • window.scrollY
  • və daha çoxu…

Həll #1: Resurs Tələb Edən Reflow Proseslərinin Keşlənməsi

Ən aydın həll yolu bu xassə və funksiyalardan istifadə ETMƏMƏKDİR. Lakin PBS onlara çox güvənir və biz onlarsız ötüşə bilmərik.

Qarşımızda qalan yeganə həll yolu bu funksiyalara edilən çağırışları mümkün qədər azaltmaqdır. Buradakı çətin hissə o idi ki, biz bu xassələrdən və funksiyalardan PBS-in müxtəlif yerlərində dəfələrlə istifadə edirdik və çox vaxt onları eyni DOM elementində dəfələrlə tətbiq edirdik. Bunu düzəltmək üçün hər bir problemli xassə/funksiya üçün öz keşləmə mexanizmimizi yaratdıq.

Məsələn, getBoundingClientRect  funksiyası bizə DOM elementinin cari yuxarı, sol, en, hündürlük, sağ və aşağı piksel ölçülərini verir. Biz bu funksiyadan PBS-də elementin konturlarını çəkəcəyimiz yeri hesablamaq üçün daima istifadə edirik. Bunun üçün yaratdığımız keşləmə funksiyası budur:

// 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  çağırmaq əvəzinə, indi pbsGetBoundingClientRect  istifadə etdik. İndi hər dəfə client rect sorğulananda gələcək istinad üçün bu dəyəri saxlayırıq, bununla da reflow başladıcılarının sayını məhdudlaşdırırıq.

Yadda saxlanılan client rect-lər etibarsız olduqda keşi təmizləmək üçün bir üsula da ehtiyacımız var idi. İstifadəçi səhifəni sürüşdürdükdə və ya ekranda elementlər əlavə etdikdə/dəyişdikdə/sildikdə saxlanan client rect-in dəyərləri dəyişir. Buna görə də, təzə client rect dəyərlərinə ehtiyacımız olduqda çağıra biləcəyimiz keş təmizləmə funksiyası yaratdıq:

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

Adətən istifadə etdiyimiz digər resurs tələb edən funksiya çağırışları üçün də eyni keşləmə mexanizmini tətbiq etdik.

Həll #2: FastDom İstifadə Edərək Oxuma və Yazmaları Qruplaşdırmaq

Həll #1-in tətbiqi PBS-in performansını artırdı, lakin bu kifayət etmədi.

Bir az daha Google-da axtarış etdikdən sonra Wilson Page-in ‘layout thrashing’-in qarşısının alınması haqqında məqaləsinə rast gəldik. Wilson deyir:

Layout Thrashing (reflow) JavaScript-in DOM-a dəfələrlə intensiv şəkildə yazıb sonra oxuması və sənəddə reflow-lara səbəb olması zamanı baş verir.

Görünür, müəyyən reflow başladan xassə və funksiyalara çağırışları məhdudlaşdırmaq kifayət deyil. DOM üzərində ardıcıl olaraq bir neçə dəfə “oxuma” və “yazma” həyata keçirdikdə, bu da brauzerdə reflow-a səbəb olur. Birbaşa Wilson-un saytından tipik bir ssenari:

// 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​

Yuxarıdakı nümunə 3 reflow-a səbəb olur, çünki yazı əməliyyatından sonra bütün səhifənin ölçüləri etibarsız olur. Bunun üçün təklif olunan həll yolu kodu yenidən sıralayaraq bütün oxuma və yazmaları bir araya toplamaqdır ki, bütün bunlar yalnız 1 brauzer reflow-una səbəb olsun:

// 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​

Yuxarıdakı kod parçası problemin həllini asan göstərir. Lakin reallıqda PBS-də reflow-a səbəb olan şeylərin oxunması və yazılması PBS-in həyat dövrü ərzində bir çox müxtəlif yerlərdə və müxtəlif vaxtlarda baş verir. Minlərlə sətir Javascript kodunu yenidən təşkil etmək mümkün deyil.

Xoşbəxtlikdən, Wilson məqaləsində bir həll yolu da təqdim etdi. O, həmin oxuma və yazmaları bir araya toplamağa kömək edən FastDom adlı kiçik bir köməkçi skript yaratdı.

Page Builder Sandwich-dən FastDom istifadə edən və həmçinin yuxarıdakı Həll #1-i ehtiva edən bəzi kodlar:

// 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-un etdiyi odur ki, o, bütün fastdom.measure  çağırışlarını və bütün fastdom.mutate  çağırışlarını birlikdə qruplaşdırır və onları asinxron şəkildə icra edir, brauzerdəki bütün reflow-ları azaltmaq üçün bütün oxuma və yazmaları effektiv şəkildə bir araya toplayır.

Həll #3: Stil Obyektlərinə Müraciət Etmək Resurs Tələb Edəndir

Biz həmçinin jsPerf-in köməyi ilə öyrəndik ki, bəzi stil əldə etmə funksiyaları digərlərindən daha sürətlidir.

PBS-də adətən elementin müəyyən bir stilinin cari dəyərini əldə etməliyik. Məsələn, yuxarı boşluğu (top margin) əldə etmək üçün adətən bundan istifadə edirik:

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

Lakin bunun daha sürətli olduğunu aşkar etdik:

var marginTop = elem.style.marginTop;​

Bununla belə, elem.style.marginTop  və getComputedStyle  çox fərqlidir. Birincisi bizə yalnız elementin sətirdaxili (inline) stilini verir və heç bir stil cədvəlini nəzərə almır; ikincisi isə həm sətirdaxili stilləri, həm də stil cədvəllərini nəzərə alır.

Bundan əlavə, hətta getComputedStyle  nəticəsi keşləndikdə belə, yenə də həqiqətən yavaş idi. Məlum oldu ki, stil obyektinin özünə müraciət etmək yavaş prosesdir.

Xoşbəxtlikdən, PBS elementlərə sətirdaxili stillər əlavə edir və sətirdaxili stillər stil cədvəllərindən (əksər vaxtlarda) üstün olduğundan, sətirdaxili stil dəyərini əldə etməyin kifayət edəcəyi hallar olacaq. Beləliklə, yuxarıdakı iki üsulu birləşdirsək, daha sürətli işləyən bir kod əldə edə bilərik:

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

Kod Təkmilləşdirmələrinin Nəticələri

Yuxarıdakı 3 həllimiz çoxlu araşdırma, sınaq və faktiki tətbiq tələb etdi (və Şriftlər xüsusiyyətimizin buraxılmasını gecikdirdi), lakin sonda hər şey buna dəydi.

Siçanı 2 saniyə ekranda hərəkət etdirməklə bağlı ilkin testimizə qayıdaq. Budur köhnə qüsurlu/donan nəticə qrafiki:

Və performans təkmilləşdirmələrindən sonra yeni nəticə qrafikimiz belə görünür:

Gördüyünüz kimi, qrafikdə daha az bənövşəyi sahə var. Brauzer tərəfindən həyata keçirilən proseslərin sayında nəzərəçarpacaq fərq var. Aşağıdakı düzbucaqlı hissələr də daha incədir, bu da proseslərin əvvəlkindən daha sürətli işlədiyini göstərir.

Köhnə və yeni qrafiklərdəki hündür/geniş bənövşəyi sahələrin miqdarını müqayisə etsəniz, təsirli bir 200% performans artımı görürük! Həmçinin hər sahədə performans artımı müşahidə edirik. Heç də pis deyil!

Nəticələr və Öyrənilənlər

Düzünü desək, Ann bunu qeyd etməzdən əvvəl PBS-in performans problemlərinin olduğunu düşünmürdük. Əsasən ona görə ki, biz tərtibat üçün güclü kompüterlərdən istifadə edirdik və performans darboğazları o qədər də nəzərə çarpmırdı. Keçmişə baxanda performansın yoxlanılması əsas prioritetlərdən biri olmalı idi. Lakin ən azı indi, 4.3 versiyasından etibarən bu həll edilmişdir. ?

Öz WordPress plaginlərinin performansını artırmaq istəyənlər üçün, xüsusən də siz də çoxlu Javascript istifadə edirsinizsə, bu təcrübədən əldə etdiyim nəticələr bunlardır:

  1. Reflow-a səbəb olan xassələrə/funksiyalara çağırışlarınızı məhdudlaşdırın/keşləyin. Bələdçi kimi bundan istifadə edin: What forces layout/reflow. The comprehensive list.
  2. DOM stillərinə edilən “oxuma” və “yazma”ları bir yerə qruplaşdırın. Bunu sizin üçün etmək üçün FastDom-dan istifadə edin.
  3. Chrome-un Timeline profiler alətindən istifadə edin və mümkün qədər az reflow-un (bənövşəyi sahələrin) olduğuna əmin olun.