Performansla% 400 daha hızlı bir performans paneli

Andrés Olivares
Andrés Olivares
Nancy Li
Nancy Li

Geliştirdiğiniz uygulamanın türü ne olursa olsun, performansını optimize etmek, hızlı yüklenmesini ve sorunsuz etkileşimler sunmasını sağlamak kullanıcı deneyimi ve uygulamanın başarısı için kritik öneme sahiptir. Bunu yapmanın bir yolu, bir zaman aralığında çalışırken arka planda neler olduğunu görmek için profilleme araçlarını kullanarak bir uygulamanın etkinliğini incelemektir. Geliştirici Araçları'ndaki Performans paneli, web uygulamalarının performansını analiz etmek ve optimize etmek için harika bir profilleme aracıdır. Uygulamanız Chrome'da çalışıyorsa uygulama yürütülürken tarayıcının ne yaptığına dair ayrıntılı bir görsel genel bakış sunar. Bu etkinliği anlamak, performansı artırmak için harekete geçebileceğiniz kalıpları, darboğazları ve performans açısından önemli noktaları belirlemenize yardımcı olabilir.

Aşağıdaki örnekte, Performans panelinin kullanımı adım adım açıklanmaktadır.

Profil oluşturma senaryomuzu ayarlama ve yeniden oluşturma

Yakın zamanda, Performans panelini daha iyi performans gösterecek şekilde geliştirme hedefi belirledik. Özellikle büyük hacimli performans verilerinin daha hızlı yüklenmesini istiyorduk. Örneğin, uzun süren veya karmaşık süreçlerin profili oluşturulurken ya da ayrıntılı veriler yakalanırken bu durum geçerlidir. Bunu başarmak için öncelikle uygulamanın nasıl çalıştığını ve neden o şekilde çalıştığını anlamak gerekiyordu. Bu da bir profil oluşturma aracı kullanılarak sağlandı.

Geliştirici Araçları'nın kendisi de bir web uygulamasıdır. Bu nedenle, Performans paneli kullanılarak profillendirilebilir. Bu panelin kendisini profillemek için Geliştirici Araçları'nı açabilir ve ardından ona bağlı başka bir Geliştirici Araçları örneği açabilirsiniz. Google'da bu kurulum Geliştirici Araçları'nda Geliştirici Araçları olarak bilinir.

Kurulum tamamlandıktan sonra, profili oluşturulacak senaryo yeniden oluşturulup kaydedilmelidir. Kafaları karıştırmamak için orijinal Geliştirici Araçları penceresi "birinci Geliştirici Araçları örneği", birinci örneği inceleyen pencere ise "ikinci Geliştirici Araçları örneği" olarak adlandırılacaktır.

Geliştirici Araçları'ndaki öğeleri inceleyen bir Geliştirici Araçları örneğinin ekran görüntüsü.
Geliştirici Araçları'nda Geliştirici Araçları: Geliştirici Araçları'nı Geliştirici Araçları ile inceleme.

İkinci Geliştirici Araçları örneğinde, Performance (Performans) paneli (bundan sonra perf paneli olarak adlandırılacak), bir profili yükleyen senaryoyu yeniden oluşturmak için ilk Geliştirici Araçları örneğini gözlemler.

İkinci DevTools örneğinde canlı kayıt başlatılırken birinci örnekte diskteki bir dosyadan profil yüklenir. Büyük girişlerin işlenmesiyle ilgili performansı doğru şekilde profillendirmek için büyük bir dosya yüklenir. Her iki örnek de yüklenmeyi tamamladığında, genellikle iz olarak adlandırılan performans profili oluşturma verileri, bir profili yükleyen perf panelinin ikinci Geliştirici Araçları örneğinde görülür.

İlk durum: İyileştirme fırsatlarını belirleme

Yükleme işlemi tamamlandıktan sonra, ikinci performans paneli örneğimizde aşağıdaki durumlar gözlemlendi. Main etiketli izde görünen ana iş parçacığının etkinliğine odaklanın. Alev grafiğinde beş büyük etkinlik grubu olduğu görülmektedir. Bunlar, yüklemenin en uzun sürdüğü görevlerden oluşur. Bu görevlerin toplam süresi yaklaşık 10 saniyeydi. Aşağıdaki ekran görüntüsünde, neler bulunabileceğini görmek için bu etkinlik gruplarının her birine odaklanmak üzere performans paneli kullanılmaktadır.

Geliştirici Araçları'ndaki performans panelinin, başka bir Geliştirici Araçları örneğinin performans panelinde performans izinin yüklenmesini inceleyen ekran görüntüsü. Profilin yüklenmesi yaklaşık 10 saniye sürer. Bu süre çoğunlukla beş ana etkinlik grubu arasında bölünür.

İlk etkinlik grubu: gereksiz çalışma

İlk etkinlik grubunun, hâlâ çalışan ancak gerçekten gerekli olmayan eski kod olduğu anlaşıldı. Temel olarak, processThreadEvents etiketli yeşil blokun altındaki her şey boşuna harcanmış çabaydı. Bu, hızlı bir kazanımdı. Bu işlev çağrısının kaldırılmasıyla yaklaşık 1,5 saniye tasarruf edildi. Güzel!

İkinci etkinlik grubu

İkinci etkinlik grubunda çözüm, ilk gruptaki kadar basit değildi. buildProfileCalls yaklaşık 0, 5 saniye sürdü ve bu görevden kaçınılamazdı.

Geliştirici Araçları'ndaki performans panelinin başka bir performans paneli örneğini incelerken çekilmiş ekran görüntüsü. buildProfileCalls işleviyle ilişkili bir görev yaklaşık 0,5 saniye sürer.

Daha ayrıntılı incelemek için merakımızı gidermek amacıyla performans panelinde Bellek seçeneğini etkinleştirdik ve buildProfileCalls etkinliğinin de çok fazla bellek kullandığını gördük. Burada, mavi çizgi grafiğin buildProfileCalls çalıştırıldığı sırada aniden değiştiğini görebilirsiniz. Bu durum, olası bir bellek sızıntısına işaret eder.

Geliştirici Araçları'ndaki bellek profil aracının, performans panelinin bellek tüketimini değerlendirdiği sırada çekilmiş ekran görüntüsü. İnceleyici, buildProfileCalls işlevinin bellek sızıntısından sorumlu olduğunu belirtiyor.

Bu şüpheyi araştırmak için Bellek panelini (DevTools'daki başka bir panel, performans panelindeki Bellek çekmecesinden farklı) kullandık. Bellek panelinde, CPU profilini yükleyen performans paneli için yığın anlık görüntüsünü kaydeden "Allocation sampling" (Ayırma örnekleme) profilleme türü seçildi.

Bellek profil oluşturucunun başlangıç durumunun ekran görüntüsü. "Tahsis örnekleme" seçeneği kırmızı bir kutuyla vurgulanır ve bu seçeneğin JavaScript bellek profili oluşturma için en iyi seçenek olduğu belirtilir.

Aşağıdaki ekran görüntüsünde toplanan yığın anlık görüntüsü gösterilmektedir.

Belleği yoğun olarak kullanan bir Set tabanlı işlemin seçildiği bellek profil oluşturucunun ekran görüntüsü.

Bu yığın anlık görüntüsünden, Set sınıfının çok fazla bellek tükettiği görülmektedir. Çağrı noktaları kontrol edildiğinde, büyük hacimlerde oluşturulan nesnelere gereksiz yere Set türünde özellikler atadığımız tespit edildi. Bu maliyet artıyordu ve çok fazla bellek tüketiliyordu. Bu durum, uygulamanın büyük girişlerde kilitlenmesine neden oluyordu.

Kümeler, benzersiz öğeleri depolamak için kullanışlıdır ve içeriklerinin benzersizliğini kullanan işlemler (ör. veri kümelerinin yinelenen öğelerini kaldırma ve daha verimli aramalar sağlama) sunar. Ancak, depolanan verilerin kaynaktan benzersiz olduğu garanti edildiğinden bu özellikler gerekli değildi. Bu nedenle, ilk etapta setlere gerek yoktu. Bellek ayırmayı iyileştirmek için özellik türü Set olan bir türden düz diziye değiştirildi. Bu değişiklik uygulandıktan sonra başka bir yığın anlık görüntüsü alındı ve bellek ayırma işleminin azaldığı görüldü. Bu değişiklikle önemli bir hız artışı elde edilmese de uygulamanın daha az sıklıkta kilitlenmesi ikincil bir avantaj oldu.

Bellek profil oluşturucunun ekran görüntüsü. Daha önce bellek açısından yoğun olan küme tabanlı işlem, düz dizi kullanacak şekilde değiştirildi. Bu değişiklik, bellek maliyetini önemli ölçüde azalttı.

Üçüncü etkinlik grubu: Veri yapısı ile ilgili avantaj ve dezavantajları değerlendirme

Üçüncü bölüm ise farklıdır: Alev grafiğinde, dar ancak uzun sütunlardan oluştuğu görülür. Bu sütunlar, derin işlev çağrılarını ve bu durumda derin özyinelemeleri gösterir. Bu bölüm toplamda yaklaşık 1, 4 saniye sürdü. Bu bölümün en altına bakıldığında, bu sütunların genişliğinin bir işlevin süresine göre belirlendiği anlaşılıyordu: appendEventAtLevel. Bu da söz konusu işlevin darboğaz olabileceğini gösteriyordu.

appendEventAtLevel işlevinin uygulanmasında bir nokta dikkat çekiyordu. Girişteki her bir veri girişi için (kodda "etkinlik" olarak bilinir) zaman çizelgesi girişlerinin dikey konumunu izleyen bir haritaya öğe ekleniyordu. Bu durum, depolanan öğe sayısı çok fazla olduğundan sorun yaratıyordu. Haritalar, anahtar tabanlı aramalar için hızlıdır ancak bu avantaj ücretsiz değildir. Bir harita büyüdükçe, örneğin yeniden karma oluşturma nedeniyle haritaya veri eklemek pahalı olabilir. Bu maliyet, haritaya art arda çok sayıda öğe eklendiğinde fark edilir.

/**
 * Adds an event to the flame chart data at a defined vertical level.
 */
function appendEventAtLevel (event, level) {
  // ...

  const index = data.length;
  data.push(event);
  this.indexForEventMap.set(event, index);

  // ...
}

Alev grafiğindeki her giriş için haritaya öğe eklememizi gerektirmeyen başka bir yaklaşım denedik. Bu iyileştirme, darboğazın gerçekten de tüm verilerin haritaya eklenmesiyle oluşan ek yükle ilgili olduğunu doğrulayacak şekilde önemliydi. Etkinlik grubunun süresi yaklaşık 1,4 saniyeden yaklaşık 200 milisaniyeye düştü.

Önce:

appendEventAtLevel işlevinde optimizasyon yapılmadan önceki performans panelinin ekran görüntüsü. İşlevin çalışması için geçen toplam süre 1.372,51 milisaniye oldu.

Sonra:

appendEventAtLevel işlevinde optimizasyonlar yapıldıktan sonraki performans panelinin ekran görüntüsü. İşlevin çalışması için geçen toplam süre 207,2 milisaniyedir.

Dördüncü etkinlik grubu: Yinelenen çalışmaları önlemek için kritik olmayan işleri erteleme ve verileri önbelleğe alma

Bu pencereye yakınlaştırdığımızda, neredeyse aynı olan iki işlev çağrısı bloğu olduğunu görebiliriz. Çağrılan işlevlerin adına bakarak bu blokların ağaçlar oluşturan kodlardan (ör. refreshTree veya buildChildren gibi adlarla) oluştuğunu anlayabilirsiniz. Aslında, ilgili kod, panelin alt çekmecesinde ağaç görünümlerini oluşturan koddur. İlginç olan, bu ağaç görünümlerinin yüklemeden hemen sonra gösterilmemesidir. Bunun yerine, ağaçların gösterilmesi için kullanıcının bir ağaç görünümü (çekmecedeki "Aşağıdan yukarıya", "Çağrı Ağacı" ve "Olay Günlüğü" sekmeleri) seçmesi gerekir. Ayrıca, ekran görüntüsünden de görebileceğiniz gibi, ağaç oluşturma işlemi iki kez yürütülmüştür.

Gerekli olmasalar bile yürütülen birkaç tekrarlayan görevi gösteren performans panelinin ekran görüntüsü. Bu görevler, önceden yürütülmek yerine talep üzerine yürütülecek şekilde ertelenebilir.

Bu resimle ilgili olarak tespit ettiğimiz iki sorun var:

  1. Kritik olmayan bir görev, yükleme süresinin performansını engelliyordu. Kullanıcıların her zaman bu çıktılara ihtiyacı yoktur. Bu nedenle, görev profil yükleme açısından kritik değildir.
  2. Bu görevlerin sonucu önbelleğe alınmadı. Bu nedenle, veriler değişmemesine rağmen ağaçlar iki kez hesaplandı.

Ağaç hesaplamasını, kullanıcının ağaç görünümünü manuel olarak açtığı zamana erteleyerek başladık. Bu ağaçları oluşturmanın maliyetini karşılamak ancak bu şekilde değerlidir. Bu işlevi iki kez çalıştırmanın toplam süresi yaklaşık 3,4 saniye olduğundan ertelemek, yükleme süresinde önemli bir fark yarattı. Bu tür görevlerin de önbelleğe alınmasıyla ilgili araştırmalarımız devam ediyor.

Beşinci etkinlik grubu: Mümkün olduğunda karmaşık görüşme hiyerarşilerinden kaçının

Bu gruba yakından baktığımızda belirli bir arama zincirinin tekrar tekrar çağrıldığı açıkça görülüyordu. Aynı desen, alev grafiğinde farklı yerlerde 6 kez göründü ve bu pencerenin toplam süresi yaklaşık 2,4 saniyeydi.

Aynı izleme minimapasını oluşturmak için altı ayrı işlev çağrısı gösteren performans panelinin ekran görüntüsü. Bu işlev çağrılarının her birinde derin çağrı yığınları vardır.

Birden çok kez çağrılan ilgili kod, verileri işleyerek "mini haritada" (panelin üst kısmındaki zaman çizelgesi etkinliğine genel bakış) oluşturulmasını sağlayan kısımdır. Bu durumun neden birden fazla kez gerçekleştiği net değildi ancak kesinlikle 6 kez gerçekleşmesi gerekmiyordu. Aslında, başka bir profil yüklenmediği sürece kodun çıkışı güncel kalmalıdır. Teorik olarak kod yalnızca bir kez çalıştırılmalıdır.

Yapılan inceleme sonucunda, yükleme işlem hattındaki birden fazla bölümün, minimapi hesaplayan işlevi doğrudan veya dolaylı olarak çağırması nedeniyle ilgili kodun çağrıldığı tespit edildi. Bunun nedeni, programın çağrı grafiğinin karmaşıklığının zaman içinde artması ve bu koda farkında olmadan daha fazla bağımlılık eklenmesidir. Bu sorunun hızlı bir çözümü yoktur. Bu sorunu çözme yöntemi, söz konusu kod tabanının mimarisine bağlıdır. Bizim durumumuzda, çağrı hiyerarşisi karmaşıklığını biraz azaltmamız ve giriş verileri değişmeden kalırsa kodun yürütülmesini önlemek için bir kontrol eklememiz gerekti. Bu özelliği uyguladıktan sonra zaman çizelgesiyle ilgili şu görünümü elde ettik:

Aynı izleme minimapasını oluşturmak için altı ayrı işlev çağrısının yalnızca iki kez yapıldığını gösteren performans panelinin ekran görüntüsü.

Minimap oluşturma işleminin bir kez değil, iki kez gerçekleştiğini unutmayın. Bunun nedeni, her profil için iki mini harita çizilmesidir: biri panelin üst kısmındaki genel bakış için, diğeri ise geçmişten şu anda görünür olan profili seçen açılır menü için (bu menüdeki her öğe, seçtiği profilin genel bakışını içerir). Ancak bu ikisi tamamen aynı içeriğe sahip olduğundan biri diğerinde yeniden kullanılabilir.

Bu mini haritaların her ikisi de tuval üzerine çizilmiş resimler olduğundan, drawImage tuval yardımcı programını kullanmak ve ardından kodu yalnızca bir kez çalıştırarak biraz daha zaman kazanmak yeterli oldu. Bu çalışma sonucunda grubun süresi 2, 4 saniyeden 140 milisaniyeye düşürüldü.

Sonuç

Tüm bu düzeltmeler (ve birkaç küçük düzeltme) uygulandıktan sonra profil yükleme zaman çizelgesindeki değişiklik aşağıdaki gibi görünüyordu:

Önce:

Optimizasyonlardan önce iz yüklemeyi gösteren performans panelinin ekran görüntüsü. Bu işlem yaklaşık on saniye sürdü.

Sonra:

Optimizasyonlardan sonra iz yüklemeyi gösteren performans panelinin ekran görüntüsü. Bu işlem artık yaklaşık iki saniye sürüyor.

İyileştirmelerden sonra yükleme süresi 2 saniye oldu. Yapılanların çoğu hızlı düzeltmelerden oluştuğu için nispeten az çabayla yaklaşık%80'lik bir iyileşme sağlandı. Elbette, başlangıçta ne yapılması gerektiğini doğru bir şekilde belirlemek çok önemliydi ve bu amaç için performans paneli doğru araçtı.

Bu sayıların, çalışma konusu olarak kullanılan bir profile özgü olduğunu da belirtmek önemlidir. Bu profil, özellikle büyük olduğu için ilgimizi çekti. Bununla birlikte, işleme hattı her profil için aynı olduğundan elde edilen önemli iyileştirme, performans panelinde yüklenen her profil için geçerlidir.

Çıkarımlar

Uygulamanızın performans optimizasyonu açısından bu sonuçlardan çıkarılacak bazı dersler vardır:

1. Çalışma zamanı performans kalıplarını belirlemek için profil oluşturma araçlarından yararlanma

Profillendirme araçları, uygulamanız çalışırken neler olduğunu anlamak ve özellikle performansı artırma fırsatlarını belirlemek için inanılmaz derecede yararlıdır. Chrome Geliştirici Araçları'ndaki Performans paneli, tarayıcıdaki yerel web profili oluşturma aracı olduğundan ve en yeni web platformu özellikleriyle güncel kalması için aktif olarak bakımı yapıldığından web uygulamaları için harika bir seçenektir. Ayrıca, artık çok daha hızlı! 😉

Temsili iş yükleri olarak kullanılabilecek örnekleri kullanın ve neler bulabileceğinize bakın.

2. Karmaşık görüşme hiyerarşilerinden kaçının

Mümkün olduğunda, çağrı grafiğinizi çok karmaşık hale getirmeyin. Karmaşık çağrı hiyerarşilerinde performans gerilemelerine neden olmak kolaydır ve kodunuzun neden bu şekilde çalıştığını anlamak zordur. Bu da iyileştirmeler yapmayı zorlaştırır.

3. Gereksiz işleri belirleme

Eskimiş kod tabanlarında artık ihtiyaç duyulmayan kodlar bulunması yaygın bir durumdur. Bizim durumumuzda, eski ve gereksiz kodlar toplam yükleme süresinin önemli bir bölümünü alıyordu. Bu özelliği kaldırmak en kolay çözümdü.

4. Veri yapılarını uygun şekilde kullanın

Performansı optimize etmek için veri yapılarını kullanın ancak hangi veri yapılarını kullanacağınıza karar verirken her bir veri yapısı türünün getirdiği maliyetleri ve ödünleri de göz önünde bulundurun. Bu, yalnızca veri yapısının kendisinin alan karmaşıklığı değil, aynı zamanda geçerli işlemlerin zaman karmaşıklığıdır.

5. Karmaşık veya tekrarlayan işlemler için yinelenen çalışmalardan kaçınmak üzere sonuçları önbelleğe alma

İşlemin yürütülmesi maliyetli ise sonuçlarını bir sonraki ihtiyaç için saklamak mantıklıdır. Bu işlem her seferinde çok maliyetli olmasa bile birçok kez yapılıyorsa bu yöntemi kullanmak mantıklıdır.

6. Kritik olmayan işleri erteleyin

Bir görevin çıktısına hemen ihtiyaç duyulmuyorsa ve görev yürütme, kritik yolu uzatıyorsa çıktısı gerçekten gerektiğinde tembelce çağırarak görevi ertelemeyi düşünebilirsiniz.

7. Büyük girişlerde verimli algoritmalar kullanma

Büyük girişler için optimum zaman karmaşıklığı algoritmaları çok önemlidir. Bu örnekte bu kategoriye bakmadık ancak önemini göz ardı etmek mümkün değildir.

8. Bonus: Ardışık düzenlerinizi karşılaştırmalı olarak değerlendirin

Gelişen kodunuzun hızlı kalmasını sağlamak için davranışı izlemeniz ve standartlarla karşılaştırmanız önerilir. Bu sayede, gerilemeleri proaktif bir şekilde belirleyip genel güvenilirliği artırarak uzun vadeli başarıya ulaşabilirsiniz.