გასულ 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 profiler ამ ამოცანისთვის საუკეთესო ხელსაწყო აღმოჩნდა. Timeline profiler-ს შეუძლია ამოიცნოს და გაზომოს, თუ რა პროცესები მიმდინარეობს ვებგვერდზე მისი მუშაობის დროს, რაც იდეალურია სიჩქარის შემაფერხებელი წერტილების აღმოსაჩენად.
ჩავთვალეთ, რომ თავდაპირველად ყველაზე მარტივი ნაბიჯები უნდა გამოგვეცადა — მაუსის გადატარება რამდენიმე ელემენტსა და მწკრივზე. მარტივია, არა? დარწმუნებულები ვიყავით, რომ ასეთ მარტივ რამეზე წარმადობის პრობლემებს არ გადავაწყდებოდით. სამწუხაროდ, შევცდით.
როგორც კი ჩაწერის ღილაკს დავაჭირეთ, მაუსი ეკრანზე დაახლოებით 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 ფუნქცია გვაძლევს 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 მოითხოვება, ჩვენ ვინახავთ ამ მნიშვნელობას სამომავლო გამოყენებისთვის და ამით ვზღუდავთ 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-ს. აი, ტიპური სცენარი პირდაპირ უილსონის საიტიდან:
// 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
ზემოთ მოყვანილი მაგალითი იწვევს 3 reflow-ს, რადგან ჩაწერის შემდეგ მთელი გვერდის ზომები ძალას კარგავს. ამის შემოთავაზებული გამოსავალია კოდის გადალაგება, რათა ყველა წაკითხვისა და ჩაწერის ოპერაცია ერთად დაჯგუფდეს, რათა მთელმა პროცესმა ბრაუზერის მხოლოდ 1 reflow გამოიწვიოს:
// 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-ში reflow-ს გამომწვევი ოპერაციების წაკითხვა და ჩაწერა ხდება უამრავ სხვადასხვა ადგილას და სხვადასხვა დროს PBS-ის სასიცოცხლო ციკლის განმავლობაში. ათასობით ხაზიანი Javascript კოდის თავიდან ორგანიზება კი პრაქტიკულად შეუძლებელია.
ჩვენდა საბედნიეროდ, უილსონმა თავის სტატიაში გამოსავალიც შემოგვთავაზა. მან შექმნა პატარა დამხმარე სკრიპტი ამ წაკითხვა-ჩაწერების ერთად დასაჯგუფებლად, სახელწოდებით 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-ის ყველა გამოძახებას და მათ ასინქრონულად ასრულებს, რითაც ეფექტურად აერთიანებს ყველა წაკითხვისა და ჩაწერის ოპერაციას ბრაუზერში reflow-ების შესამცირებლად.
გამოსავალი #3: სტილის ობიექტებთან წვდომა ძვირადღირებულია
ასევე, jsPerf-ის დახმარებით აღმოვაჩინეთ, რომ სტილის მიმღები ზოგიერთი ფუნქცია სხვებზე სწრაფი იყო.
PBS-ში ხშირად გვჭირდება ელემენტის კონკრეტული სტილის მიმდინარე მნიშვნელობის მიღება. მაგალითად, ზედა დაშორების (top margin) მისაღებად ჩვეულებრივ ამას ვიყენებთ:
var styles = window.getComputedStyle( elem ); var marginTop = styles.marginTop;
მაგრამ აღმოვაჩინეთ, რომ ასე გაკეთება ბევრად უფრო სწრაფია:
var marginTop = elem.style.marginTop;
თუმცა, elem.style.marginTop და getComputedStyle ძალიან განსხვავდება ერთმანეთისგან. პირველი გვაძლევს მხოლოდ ელემენტის inline სტილს და არ ითვალისწინებს სტილების ცხრილებს (stylesheets); ხოლო მეორე ითვალისწინებს როგორც inline სტილებს, ისე stylesheets-ს.
უფრო მეტიც, მაშინაც კი, როდესაც getComputedStyle-ის შედეგი დაქეშირებული იყო, ის მაინც ძალიან ნელი იყო. როგორც გაირკვა, უშუალოდ სტილის ობიექტთან წვდომაა ნელი პროცესი.
ჩვენდა საბედნიეროდ, PBS ელემენტებს inline სტილებს უმატებს, და რადგან inline სტილებს (უმეტესწილად) უპირატესობა ენიჭებათ stylesheets-თან შედარებით, იქნება შემთხვევები, როდესაც მხოლოდ inline სტილის მნიშვნელობის მიღებაც სრულიად საკმარისი იქნება. ამდენად, თუ ზემოხსენებულ ორ მეთოდს გავაერთიანებთ, მივიღებთ ბევრად უფრო სწრაფად მომუშავე კოდს:
var marginTop = elem.style.marginTop;
if ( ! marginTop ) {
var styles = window.getComputedStyle( elem );
marginTop = styles.marginTop;
}
კოდის გაუმჯობესების შედეგები
ზემოთ ჩამოთვლილმა 3-მა გამოსავალმა დიდი კვლევა, ტესტირება და რეალური იმპლემენტაცია მოითხოვა (და გადადო ჩვენი ფონტების ფუნქციის გამოშვება), მაგრამ საბოლოოდ ეს ნამდვილად ღირდა.
დავუბრუნდეთ ჩვენს საწყის ტესტს, სადაც მაუსს ეკრანზე 2 წამის განმავლობაში ვამოძრავებდით. აი, ძველი, შეფერხებებით სავსე დროის ხაზი:
ხოლო აი, როგორ გამოიყურება ჩვენი ახალი დროის ხაზი წარმადობის გაუმჯობესების შემდეგ:
როგორც ხედავთ, გრაფიკზე იასამნისფერი ელემენტები გაცილებით ნაკლებია. ასევე შესამჩნევია განსხვავება ბრაუზერის მიერ შესრულებული პროცესების რაოდენობაშიც. ქვედა ნაწილში არსებული მართკუთხა ველებიც უფრო თხელია, რაც იმას ნიშნავს, რომ პროცესები წინანდელთან შედარებით ბევრად უფრო სწრაფად სრულდება.
თუ შევადარებთ მაღალი/განიერი იასამნისფერი არეალების რაოდენობას ძველ და ახალ გრაფიკებზე, ჩვენ ვხედავთ წარმადობის შთამბეჭდავ 200%-იან ზრდას! ჩვენ ასევე ვხედავთ წარმადობის გაუმჯობესებას ყველა მიმართულებით. სულაც არ არის ცუდი!
დასკვნები და მიღებული გამოცდილება
სიმართლე რომ ვთქვათ, სანამ ენი ამ საკითხს წამოჭრიდა, არ გვეგონა, რომ PBS-ს წარმადობის პრობლემები ჰქონდა. ძირითადად იმიტომ, რომ დეველოპმენტისთვის განკუთვნილ მძლავრ კომპიუტერებს ვიყენებდით და წარმადობის შეფერხებები ასე თვალშისაცემი არ იყო. უკან რომ ვიხედებით, წარმადობის შემოწმება ერთ-ერთი მთავარი პრიორიტეტი უნდა ყოფილიყო. თუმცა, ახლა მაინც, 4.3 ვერსიიდან მოყოლებული, ეს საკითხი მოგვარებულია.
მათთვის, ვისაც საკუთარი WordPress-ის პლაგინის წარმადობის გაუმჯობესება სურს, განსაკუთრებით თუ თქვენც ინტენსიურად იყენებთ Javascript-ს, გიზიარებთ ჩემს დასკვნებს ამ გამოცდილებიდან:
- შეზღუდეთ/დააქეშეთ იმ თვისებებისა/ფუნქციების გამოძახებები, რომლებიც იწვევენ reflow-ს. გამოიყენეთ ეს გზამკვლევად: What forces layout/reflow. The comprehensive list.
- დააჯგუფეთ DOM სტილებზე „წაკითხვისა“ და „ჩაწერის“ ოპერაციები. გამოიყენეთ FastDom ამის გასაკეთებლად.
- გამოიყენეთ Chrome-ის Timeline profiler და დარწმუნდით, რომ რაც შეიძლება ცოტა reflow (იასამნისფერი ელემენტები) გაქვთ.