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 hem kullanıcı deneyimi hem de uygulamanın başarısı için çok önemlidir. Bunu yapmanın bir yolu, bir zaman aralığında çalışırken uygulamanın altında neler olduğunu görmek için profilleme araçlarını kullanarak 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 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 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 sorunlarını belirlemenize yardımcı olabilir.
Aşağıdaki örnekte, Performans panelini kullanma konusunda size yol gösterilir.
Profil oluşturma senaryomuzu ayarlama ve yeniden oluşturma
Yakın zamanda Performans panelini daha performanslı hale getirmeyi hedefledik. Özellikle büyük hacimli performans verilerini daha hızlı yüklemesini istedik. Örneğin, uzun süren veya karmaşık süreçlerin profilini çıkarırken ya da yüksek ayrıntı düzeyinde verileri yakalarken bu durum söz konusudur. Bunu başarmak için öncelikle uygulamanın nasıl performans gösterdiği ve neden bu şekilde performans gösterdiğinin anlaşılması gerekiyordu. Bu da bir profil oluşturma aracı kullanılarak sağlandı.
Bildiğiniz gibi Geliştirici Araçları da bir web uygulamasıdır. Bu nedenle, Performans paneli kullanılarak profillenebilir. Bu panelin profilini oluşturmak için Geliştirici Araçları'nı ve ardından buna bağlı başka bir Geliştirici Araçları örneğini açabilirsiniz. Google'da bu kurulum DevTools üzerinde DevTools olarak bilinir.
Kurulum hazır olduğunda, profil oluşturulacak senaryonun yeniden oluşturulması ve kaydedilmesi gerekir. 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.
İkinci DevTools örneğinde, Performans paneli (bundan sonra perf paneli olarak anılacaktır) bir profil yükleyen senaryoyu yeniden oluşturmak için ilk DevTools örneğini gözlemler.
İkinci DevTools örneğinde canlı kayıt başlatılırken ilk örnekte diskteki bir dosyadan profil yüklenir. Büyük girişlerin işlenme performansını doğru şekilde profillemek için büyük bir dosya yüklenir. Her iki örnek de yüklendiğinde, performans profilleme verileri (genellikle iz olarak adlandırılır) bir profil yükleyen performans panelinin ikinci DevTools örneğinde gösterilir.
Başlangıç durumu: İyileştirme fırsatlarını belirleme
Yükleme işlemi tamamlandıktan sonra, aşağıdaki 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 mesaj dizisinin etkinliğine odaklanın. Alev 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 saniye idi. Aşağıdaki ekran görüntüsünde, performans paneli kullanılarak bu etkinlik gruplarının her birine odaklanıp neler bulunabileceğini görebilirsiniz.
İlk etkinlik grubu: Gereksiz çalışma
İlk etkinlik grubunun, hâlâ çalışan ancak aslında gerekli olmayan eski kod olduğu anlaşıldı. Temel olarak, processThreadEvents
etiketli yeşil bloğun altındaki her şey boşa çabaydı. Bu, hızlı bir kazançtı. Bu işlev çağrısının kaldırılmasıyla yaklaşık 1,5 saniyelik bir zaman tasarrufu sağlandı. 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ı.
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ığı sırada mavi çizgi grafiğin aniden nasıl sıçradığını görebilirsiniz. Bu durum, olası bir bellek sızıntısı olduğunu gösterir.
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, CPU profilini yükleyen performans paneli için yığın anlık görüntüsünü kaydeden "Ayrıntılandırma örnekleme" profilleme türü seçildi.
Aşağıdaki ekran görüntüsünde, toplanan yığın anlık görüntüsü gösterilmektedir.
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 yere 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.
Kümeler, benzersiz öğeleri depolamak için kullanışlıdır ve içeriklerinin benzersizliğini kullanan işlemler (ör. veri kümelerini tekilleştirme ve daha verimli aramalar sağlama) sunar. Ancak, depolanan verilerin kaynaktan benzersiz olması garanti edildiğinden bu özellikler gerekli değildi. Bu nedenle, başlangıçta setlere gerek yoktu. Bellek ayırmayı iyileştirmek için mülk türü Set
yerine düz bir 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 azaltılmış bellek ayırma işlemi gözlemlendi. Bu değişiklikle önemli bir hız artışı elde edemesek de ikincil bir avantaj olarak uygulamanın daha seyrek kilitlenmesini sağladık.
Üçüncü etkinlik grubu: Veri yapısıyla ilgili avantaj ve dezavantajları değerlendirme
Üçüncü bölüm tuhaf: Alev grafiğinde, bu bölümün dar ancak yüksek sütunlardan oluştuğunu görebilirsiniz. Bu sütunlar, derin işlev çağrılarını ve bu durumda derin yinelemeleri gösterir. Bu bölüm toplamda yaklaşık 1, 4 saniye sürdü. Bu bölümün alt kısmı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 bir darboğaz olabileceğini gösteriyordu.
appendEventAtLevel
işlevinin uygulanmasında göze çarpan bir nokta vardı. 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 öğelerin miktarı çok fazla olduğu için bu durum sorunluydu. Haritalar, anahtar tabanlı aramalar için hızlıdır ancak bu avantaj ücretsiz değildir. Harita büyüdükçe, örneğin yeniden karıştırma nedeniyle haritaya veri eklemek pahalı hale gelebilir. 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 önemli bir gelişmeydi ve darboğazın gerçekten de tüm verilerin haritaya eklenmesiyle ortaya çıkan ek maliyetle ilgili olduğunu doğruladı. Etkinlik grubunun sürdüğü süre yaklaşık 1,4 saniyeden yaklaşık 200 milisaniyeye düştü.
Önce:
Sonra:
Dördüncü etkinlik grubu: Yinelenen çalışmaları önlemek için kritik olmayan çalışmaları erteleme ve verileri önbelleğe alma
Bu pencereyi yakınlaştırdığınızda neredeyse aynı iki işlev çağrısı bloğu olduğunu görebilirsiniz. Çağrılan işlevlerin adına bakarak bu blokların ağaç oluşturan koddan (örneğin, refreshTree
veya buildChildren
gibi adlara sahip) 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, kullanıcının ağaçların gösterilmesi için bir ağaç görünümü seçmesi gerekir ("Aşağıdan yukarıya", "Çağrı ağacı" ve "Olay günlüğü" sekmeleri). 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.
Bu resimde tespit ettiğimiz iki sorun var:
- Kritik olmayan bir görev, yükleme süresinin performansını engelliyordu. Kullanıcıların her zaman bu işlevin çıktısına ihtiyacı yoktur. Bu nedenle, görev profilin yüklenmesi için kritik değildir.
- Bu görevlerin sonucu önbelleğe alınmadı. Bu nedenle, veriler değişmemesine rağmen ağaçlar iki kez hesaplandı.
Ağ hesaplamasını, kullanıcı ağaç görünümünü manuel olarak açtığında ertelemeye başladık. Ancak bu durumda bu ağaçları oluşturmanın bedelini ödemeye değer. Bu işlemi iki kez çalıştırmanın toplam süresi yaklaşık 3,4 saniyeydi.Bu nedenle, işlemi 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 gruba yakından baktığımızda, belirli bir çağrı 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.
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ığı net değildi ancak 6 kez yaşanması kesinlikle gerekmiyordu. Aslında, başka bir profil yüklenmezse kodun çıkışı güncel kalır. Teorik olarak, kod yalnızca bir kez çalışmalıdır.
İnceleme sonucunda, ilgili kodun, yükleme ardışık düzenindeki birden fazla parçanın doğrudan veya dolaylı olarak mini haritayı hesaplayan işlevi çağırması sonucunda ç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. Bu 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 çizelgesinin görünümü şu şekilde oldu:
Küçük harita oluşturma işleminin bir kez değil, iki kez gerçekleştiğini unutmayın. Bunun nedeni, her profil için iki küçük harita çizilmesidir: biri panelin üst kısmındaki genel bakış için, diğeri ise tarihten seçilen profili gösteren açılır menü için (bu menüdeki her öğe, seçtiği profile genel bir bakış içerir). Yine de bu iki öğenin içeriği tamamen aynı olduğundan biri diğeri için yeniden kullanılabilir.
Bu mini haritalar bir kanvas üzerine çizilmiş resimler olduğundan, drawImage
kanvas yardımcı programını kullanmak ve ardından biraz zaman kazanmak için kodu yalnızca bir kez çalıştırmak gerekiyordu. Bu çalışma 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:
Sonra:
İyileştirmelerden sonra yüklenme süresi 2 saniye oldu. Yani yapılanların çoğu hızlı düzeltmelerden oluştuğu için nispeten az çabayla yaklaşık%80 oranında bir iyileşme elde edildi. Elbette, başlangıçta ne yapılacağını doğru şekilde belirlemek önemliydi ve performans paneli bunun için doğru araçtı.
Bu sayıların, çalışma konusu olarak kullanılan bir profile özel olduğunu da belirtmek ö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 çıkarılabilecek bazı dersler vardır:
1. Çalışma zamanı performans kalıplarını belirlemek için profil oluşturma araçlarını kullanın
Profil oluşturma araçları, uygulamanız çalışırken neler olduğunu anlamak ve özellikle performansı artırma fırsatlarını belirlemek için 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. Ayrıca, artık çok daha hızlı. 😉
Temsil edici iş yükleri olarak kullanılabilecek örnekleri kullanın ve neler bulabileceğinize bakı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, performans gerilemelerini kolayca ortaya çıkarır ve kodunuzun neden bu şekilde çalıştığını anlamanızı zorlaştırır. Bu da iyileştirme yapmayı zorlaştırır.
3. Gereksiz işleri belirleme
Eskiyen kod tabanlarının artık ihtiyaç duyulmayan kod içermesi yaygın bir durumdur. Bizim durumumuzda, eski ve gereksiz kod toplam yükleme süresinin önemli bir bölümünü alıyordu. Bu sorunun çözümü kolaydı.
4. Veri yapılarını uygun şekilde kullanın
Performansı optimize etmek için veri yapılarını kullanın ancak hangilerini kullanacağınıza karar verirken her veri yapısı türünün getirdiği maliyetleri ve avantajları da anlayın. Bu, yalnızca veri yapısının alan karmaşıklığı değil, aynı zamanda geçerli işlemlerin zaman karmaşıklığıdır.
5. Karmaşık veya tekrar eden işlemler için yinelenen çalışmalardan kaçınmak amacıyla sonuçları önbelleğe alma
İşlemin yürütülmesi maliyetliyse sonuçlarının bir sonraki sefere ihtiyaç duyulduğunda saklanması mantıklı olur. İşlem çok kez yapılıyorsa (her işlem özellikle maliyetli olmasa bile) bunu yapmak da mantıklıdır.
6. Kritik olmayan işleri erteleme
Bir görevin sonucuna hemen ihtiyaç yoksa ve görev yürütme kritik yolu uzatıyorsa sonucuna gerçekten ihtiyaç duyulduğunda tembel olarak çağırarak görevi erteleyin.
7. Büyük girişlerde verimli algoritmalar kullanın
Büyük girişler için optimum zaman karmaşıklığı algoritmaları çok önemlidir. Bu örnekte söz konusu kategoriyi ele almadık, ancak önemleri hafife alınamaz.
8. Bonus: Ardışık düzenlerinizi karşılaştırın
Gelişmekte olan kodunuzun hızlı kalmasını sağlamak için davranışı izlemek ve standartlarla karşılaştırmak akıllıca bir harekettir. Bu sayede, gerileme noktalarını proaktif olarak tespit edebilir ve genel güvenilirliği artırarak uzun vadeli başarı elde edebilirsiniz.