Performansla% 400 daha hızlı bir performans paneli

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

Ne tür bir uygulama geliştirdiğinize bakılmaksızın, uygulamanın performansını optimize etmek, hızlı yüklendiğinden ve sorunsuz etkileşim sunduğundan emin olmak kullanıcı deneyimi ve uygulamanın başarısı için kritik öneme sahiptir. Bunu yapmanın bir yolu, bir uygulama belirli bir zaman aralığında çalışırken arka planda neler olup bittiğini görmek için profil oluşturma araçlarını kullanarak uygulamanın etkinliğini incelemektir. Geliştirici Araçları'ndaki Performans paneli, web uygulamalarının performansını analiz ve optimize etmek için mükemmel bir profil oluşturma aracıdır. Uygulamanız Chrome'da çalışıyorsa, uygulamanız yürütülürken tarayıcının neler yaptığına dair ayrıntılı bir görsel genel bakış sağlar. Bu etkinliği anlamak; performansı artırmak için üzerinde işlem yapabileceğiniz kalıpları, performans sorunlarını ve performans noktalarını belirlemenize yardımcı olabilir.

Aşağıdaki örnekte, Performans panelini kullanma konusunda size yol gösterilir.

Profil çıkarma senaryomuzu oluşturma ve yeniden oluşturma

Kısa süre önce, Performans panelini daha yüksek performanslı hale getirmeyi hedefleyen bir hedef belirledik. Özellikle, büyük hacimli performans verilerini daha hızlı yüklemesini istedik. Bu durum, örneğin uzun süreli veya karmaşık süreçlerin profilini çıkarırken ya da yüksek ayrıntı düzeyine sahip verileri yakalarken geçerlidir. Bunu başarmak için uygulamanın nasıl performans gösterdiği ve bu şekilde performans neden sağlandığı hakkında bilgi sahibi olmanız gerekti. Bunun için profil oluşturma aracı kullanıldı.

Bildiğiniz gibi Geliştirici Araçları'nın kendisi bir web uygulamasıdır. Dolayısıyla, Performans paneli kullanılarak profili oluşturulabilir. Bu panelin profilini çıkarmak için Geliştirici Araçları'nı açıp ona ekli başka bir Geliştirici Araçları örneğini açabilirsiniz. Google'da bu kurulum, DevTools-on-DevTools (Dev Araçlarında-Dev Araçları) olarak bilinir.

Kurulum hazır olduğunda, profili oluşturulacak senaryo yeniden oluşturulmalı ve kaydedilmelidir. Karışıklığı önlemek için orijinal Geliştirici Araçları penceresi, "ilk Geliştirici Araçları örneği", ilk örneği inceleyen pencere ise "ikinci Geliştirici Araçları örneği" olarak adlandırılır.

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

İkinci Geliştirici Araçları örneğinde, bundan sonra perf paneli olarak adlandırılacak olan Performans paneli, senaryoyu yeniden oluşturup profil yükleyen ilk Geliştirici Araçları örneğini gözlemler.

İkinci Geliştirici Araçları örneğinde canlı kayıt başlatılır, ilk örnekte ise diskteki bir dosyadan profil yüklenir. Büyük girişlerin işlenmesinin performansını doğru şekilde incelemek için büyük bir dosya yüklenir. Her iki örneğin de yüklenmesi bittiğinde, profil yükleyen perf panelinin ikinci Geliştirici Araçları örneğinde, genellikle iz olarak adlandırılan performans profili oluşturma verileri görünür.

Başlangıç durumu: İyileştirme fırsatlarını belirleme

Yükleme tamamlandıktan sonra, bir sonraki ekran görüntüsünde ikinci performans paneli örneğimizde aşağıdaki durum gözlemlendi. Ana etiketli kanalın altında görünen ana ileti dizisinin etkinliğine odaklanın. Flame grafiğinde beş büyük etkinlik grubu olduğu görülebilir. Bunlar, yüklemenin en çok zaman aldığı görevlerden oluşur. Bu görevlerin toplam süresi yaklaşık 10 saniyeydi. Aşağıdaki ekran görüntüsünde performans paneli, ne bulunabileceğini görmek amacıyla bu etkinlik gruplarının her birine odaklanmak için kullanılıyor.

Başka bir Geliştirici Araçları örneğinin performans panelindeki performans izinin yüklenmesini inceleyen Geliştirici Araçları performans panelinin ekran görüntüsü. Profilin yüklenmesi yaklaşık 10 saniye sürer. Bu süre genelde beş ana etkinlik grubuna bölünür.

Birinci etkinlik grubu: Gereksiz iş

İlk etkinlik grubunun hâlâ çalışan ancak aslında gerekli olmayan eski bir kod olduğu anlaşıldı. Kısacası, processThreadEvents etiketli yeşil blokun altındaki her şey boşa gidiyordu. Bu hızlı bir başarı oldu. İşlev çağrısının kaldırılması yaklaşık 1,5 saniye kazandırdı. Güzel!

İkinci etkinlik grubu

İkinci etkinlik grubunda çözüm ilkindeki kadar basit değildi. buildProfileCalls yaklaşık 0, 5 saniye sürdü ve bu kaçınılmaz bir görevdi.

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

Merak ettiğimizden, daha ayrıntılı bir araştırma yapmak için performans panelindeki Bellek seçeneğini etkinleştirdik ve buildProfileCalls etkinliğinin de çok fazla bellek kullandığını gördük. Burada, buildProfileCalls çalıştırıldığında mavi çizgi grafiğinin aniden nasıl yükseldiğini görebilirsiniz. Bu durum, olası bir bellek sızıntısı olduğu anlamına gelir.

Geliştirici Araçları'ndaki performans panelinin bellek tüketimini değerlendiren bellek profili oluşturucunun ekran görüntüsü. İnceleyici, buildProfileCalls işlevinin bellek sızıntısından sorumlu olduğunu önerir.

Bu şüphenin devamı için araştırma yaparken Bellek panelini (Geliştirici Araçları'nda bulunan ve performans panelindeki Bellek çekmecesinden farklı başka bir panel) kullandık. Bellek panelinde "Tahsis örnekleme" CPU profilini yükleyen performans paneli için yığın anlık görüntüsünü kaydeden profil oluşturma türü seçildi.

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

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

Yoğun bellek kullanan Küme tabanlı işlemin seçili olduğu bellek profili oluşturucunun ekran görüntüsü.

Bu yığın anlık görüntüsünde, Set sınıfının çok fazla bellek kullandığı gözlemlendi. Çağrı noktalarını kontrol ettiğimizde, büyük hacimlerde oluşturulmuş nesnelere gereksiz şekilde Set türü özellikler atadığımızı tespit ettik. Bu maliyetler artıyor ve çok fazla bellek tüketiliyordu. Bu da uygulamanın büyük girişlerde kilitlenmesi yaygın bir durumdur.

Gruplar, benzersiz öğelerin depolanması ve içeriklerinin benzersizliğini kullanan işlemler (ör. veri kümelerini çoğaltma ve daha verimli aramalar yapma) sağlamak için kullanışlıdır. Ancak, depolanan verilerin kaynaktan benzersiz olması garanti edildiğinden bu özellikler gerekli değildi. Bu nedenle, setler en başta gerekli değildi. Bellek ayırmayı iyileştirmek için Set olan mülk türü, düz dizi olarak 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 oranı azaldı. Bu değişiklikle birlikte hızda önemli bir artış sağlanmasa da ikinci faydası, uygulamanın daha az kilitlenmesiydi.

Bellek profili oluşturucunun ekran görüntüsü. Önceden yoğun bellek kullanan Küme tabanlı işlem, bellek maliyetini önemli ölçüde azaltan düz dizi kullanacak şekilde değiştirildi.

Üçüncü etkinlik grubu: Veri yapısı dengelerini değerlendirme

Üçüncü bölüm kendine özgüdür: flame grafiğinde dar ama uzun sütunlardan oluştuğunu görebilirsiniz. Bu sütunlar, derin işlev çağrılarını ve bu örnekte derin yinelemeleri gösterir. Bu bölüm toplamda yaklaşık 1, 4 saniye sürüyor. Bu bölümün alt kısmına baktığımızda, bu sütunların genişliğinin bir işlevin süresiyle belirlendiği anlaşılmıştır: appendEventAtLevel. Bu da sorunun bir performans sorunu olabileceğini gösteriyordu

appendEventAtLevel işlevi uygulanırken bir şey ön plana çıktı. Girişteki (kodda "etkinlik" olarak bilinir) her bir veri girişi için zaman çizelgesi girişlerinin dikey konumunu izleyen haritaya bir öğe eklenmiştir. Depolanan öğe miktarı çok büyük olduğu için bu durum sorunluydu. Haritalar, anahtar tabanlı aramalar için hızlıdır ancak bu avantaj ücretsiz değildir. Bir harita büyüdükçe, buraya veri eklemek, örneğin yeniden karma oluşturma işlemi nedeniyle maliyetli olabilir. Haritaya art arda büyük miktarda öğe eklendiğinde bu maliyet fark edilebilir hale gelir.

/**
 * 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);

  // ...
}

Yanma grafiğindeki her giriş için haritaya bir öğe eklememizi gerektirmeyen başka bir yaklaşım denedik. Bu önemli iyileştirme, performans sorununun gerçekten de haritaya tüm verilerin eklenmesinden kaynaklanan genel giderlerle ilgili olduğunu doğruluyordu. Etkinlik grubunun yaklaşık 1,4 saniyeden yaklaşık 200 milisaniyeye düştüğü süre.

Önce:

InsertEventAtLevel işlevine optimizasyonlar yapılmadan önceki performans panelinin ekran görüntüsü. İşlevin toplam çalışma süresi 1.372,51 milisaniyedir.

Sonra:

InsertEventAtLevel işlevinde optimizasyonlar yapıldıktan sonraki performans panelinin ekran görüntüsü. İşlevin toplam çalışma süresi 207,2 milisaniyedir.

Dördüncü etkinlik grubu: Yinelenen işleri önlemek için kritik olmayan iş ve önbellek verilerini erteleme

Bu pencereyi yakınlaştırdığınızda, neredeyse tamamen aynı olan iki işlev çağrısı bloğu olduğu görülebilir. Çağrılan işlevlerin adına bakarak bu blokların, ağaçlar oluşturan bir koddan (örneğin, 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üklendikten 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ı", "Arama Ağacı" ve "Etkinlik Günlüğü" sekmeleri) seçmesi gerekir. Ayrıca, ekran görüntüsünden görebileceğiniz gibi, ağaç inşa etme işlemi iki kez yürütülmüştür.

İhtiyaç duyulmasa bile yürütülen birkaç yinelenen görevi gösteren performans panelinin ekran görüntüsü. Bu görevler, önceden değil talep üzerine yürütülecek şekilde ertelenebilir.

Bu resimle ilgili tespit ettiğimiz iki sorun var:

  1. Kritik olmayan bir görev, yükleme süresinin performansını engelliyordu. Kullanıcılar, çıktılarına her zaman ihtiyaç duymaz. Bu nedenle görev, profil yükleme için kritik öneme sahip değildir.
  2. Bu görevlerin sonucu önbelleğe alınmamıştır. 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 ertelemeye başladık. Bu ağaçları oluşturmanın bedelini ancak o zaman ödemeye değer. Bu oyunu iki kez çalıştırmanın toplam süresi yaklaşık 3,4 saniyeydi.Dolayısıyla bu uygulamayı ertelemek, yükleme süresinde önemli bir fark yarattı. Bu tür görevlerin önbelleğe alınmasını da araştırmaya devam ediyoruz.

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

Bu grubu yakından incelediğimizde, belirli bir çağrı zincirinin tekrar tekrar çağrıldığı anlaşılmıştır. Aynı kalıp, flame grafiğinin farklı yerlerinde 6 kez göründü ve bu pencerenin toplam süresi yaklaşık 2,4 saniyeydi.

Her biri derin çağrı yığınlarına sahip olan, aynı iz mini haritasını oluşturmak için altı ayrı işlev çağrısını gösteren performans panelinin ekran görüntüsü.

Birden çok kez çağrılan ilgili kod, "mini haritada" oluşturulacak verileri işleyen kısımdır (panelin üst kısmındaki zaman çizelgesi etkinliğine genel bakış). Bu durumun neden birden fazla kez yaşandığı anlaşılmadı ancak bu durumun 6 kez tekrarlanmasına gerek yoktu. Aslında, başka bir profil yüklenmezse kod çıkışı güncel kalmalıdır. Teoride, kod yalnızca bir kez çalıştırılmalıdır.

Yapılan incelemede, ilgili kodun yükleme ardışık düzenindeki birden fazla parçanın doğrudan veya mini haritayı hesaplayan işlevin dolaylı olarak çağrılması sonucu çağrıldığı tespit edildi. Bunun nedeni, programın çağrı grafiğinin zamanla karmaşıklığının zamanla değişmesi ve farkında olmadan bu koda daha fazla bağımlılığın eklenmesidir. Bu sorun için hızlı bir çözüm yoktur. Sorunun çözümü, söz konusu kod tabanının mimarisine bağlıdır. Bizim örneğimizde, ç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 denetim eklememiz gerekiyordu. Bunu uyguladıktan sonra zaman çizelgesindeki şu genel bakışı elde ettik:

Performans panelinin, aynı iz mini haritasını oluşturmaya yönelik altı ayrı işlev çağrısını gösteren ekran görüntüsü, yalnızca iki kez azaltıldı.

Mini harita oluşturma işleminin bir kez değil, iki kez gerçekleştirildiğ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 de geçmişten o anda görünür olan profili seçen açılır menü içindir (bu menüdeki her öğe kendi seçtiği profilin bir özetini içerir). Yine de bu iki uygulama aynı içeriğe sahip olduğundan bunlardan birinin diğeri için yeniden kullanılabilmesi gerekir.

Bu mini haritaların her ikisi de tuvale çizilmiş resimler olduğundan, drawImage tuval yardımcı programını kullanmak ve daha sonra zamandan tasarruf etmek için kodu yalnızca bir kez çalıştırmak yeterliydi. Bu çalışmanın sonucunda, grubun süresi 2,4 saniyeden 140 milisaniyeye düşürüldü.

Sonuç

Tüm bu düzeltmeleri (ve başka birkaç küçük düzeltmeyi) uyguladıktan sonra, profil yükleme zaman çizelgesindeki değişiklik aşağıdaki gibi göründü:

Önce:

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

Sonra:

Optimizasyonlardan sonra iz yüklemesini gösteren performans panelinin ekran görüntüsü. İşlem şu anda yaklaşık iki saniye sürüyor.

İyileştirmelerden sonraki yükleme süresi 2 saniyeydi. Yani, yapılan işlemlerin çoğu hızlı düzeltmelerden oluştuğu için nispeten daha az çabayla yaklaşık%80 oranında bir iyileşme sağlandı. Elbette başlangıçta ne yapılacağını doğru şekilde belirlemek büyük önem taşıyordu ve performans paneli bunun için doğru araçtı.

Bu rakamların, araştırma konusu olarak kullanılan bir profile özel olduğunun altını çizmek de önemlidir. Profil çok büyük olduğu için bizim için ilginçti. Bununla birlikte, işleme ardışık düzeni her profil için aynı olduğundan elde edilen önemli iyileştirme, performans paneline yüklenen her profil için geçerlidir.

Çıkarımlar

Uygulamanızın performans optimizasyonu açısından bu sonuçlardan çıkarabileceğiniz bazı dersler vardır:

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

Uygulamanız çalışırken uygulamanızda neler olduğunu anlamanız, özellikle de performansı artırma fırsatlarını belirlemeniz açısından profil oluşturma araçları son derece yararlıdır. Chrome Geliştirici Araçları'ndaki Performans paneli, tarayıcıdaki yerel web profili oluşturma aracı olduğundan ve en son web platformu özelliklerini yansıtacak şekilde etkin bir şekilde korunduğundan web uygulamaları için mükemmel bir seçenektir. Üstelik artık çok daha hızlı! 😉

Temsili iş yükü olarak kullanılabilecek örnekleri kullanın ve neler bulabileceğinizi görün.

2. Karmaşık çağrı hiyerarşilerinden kaçının

Mümkün olduğunda, çağrı grafiğinizi çok karmaşık hale getirmekten kaçının. Karmaşık çağrı hiyerarşileri sayesinde, performans regresyonlarını kolayca ortaya koyabilir, kodunuzun neden bu şekilde çalıştığını anlamak zorlaşır. Bu da iyileştirme bulmayı zorlaştırır.

3. Gereksiz işleri belirleyin

Eskiyen kod tabanlarının artık ihtiyaç duyulmayan kod içermesi yaygın bir durumdur. Bizim örneğimizde, eski ve gereksiz kodlar toplam yükleme süresinin önemli bir kısmını oluşturuyordu. Onu sökmek, işin en zor meyvesiydi.

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

Performansı optimize etmek için veri yapılarını kullanın. Bununla birlikte, her bir veri yapısının hangilerinin kullanılacağına karar verirken getirdiği maliyetleri ve dengeleri de anlayın. Burada yalnızca veri yapısının alan karmaşıklığı değil, aynı zamanda uygulanabilir işlemlerin zaman karmaşıklığı da önemlidir.

5. Karmaşık veya tekrarlanan işlemlerde yinelenen işleri önlemek için sonuçları önbelleğe alın

İşlemin yürütülmesi maliyetliyse sonuçlarını bir dahaki sefere gerektiğinde depolamak mantıklıdır. İşlemin birçok kez yapılması durumunda (her bir işlem çok maliyetli olmasa bile) bunu yapmak mantıklıdır.

6. Kritik olmayan işleri erteleyin

Bir görevin çıktısına hemen ihtiyaç duyulmuyorsa ve görevin yürütülmesi kritik yolu genişletiyorsa, görevin gerçekten gerekli olduğu durumlarda işi geciktirerek süreci ertelemeyi tercih edebilirsiniz.

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

Büyük girişler için optimum zaman karmaşıklığı algoritmaları son derece önemlidir. Bu örnekte bu kategoriyi ele almadık, ancak önemleri azımsanabilir.

8. Bonus: Ardışık düzenlerinizi karşılaştırın

Gelişen kodunuzun hızlı kalmasını sağlamak için davranışı izleyip standartlarla karşılaştırmak akıllıca bir yaklaşımdır. Bu sayede regresyonları proaktif olarak belirleyebilir ve genel güvenilirliğinizi artırarak uzun vadeli başarı için hazırlıklarınızı yapabilirsiniz.