Geliştirdiğiniz uygulama 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ı iyileştirmek için harekete geçebileceğiniz kalıpları, darboğazları ve performans sorunlarını belirlemenize yardımcı olabilir.
Aşağıdaki örnekte, Performans panelinin kullanımı açıklanmaktadır.
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. Bu hedefe ulaşmak için öncelikle uygulamanın nasıl ve neden bu şekilde performans gösterdiğini anlamak gerekiyordu. Bu da bir profil oluşturma aracı kullanılarak gerçekleştirildi.
Bildiğiniz gibi DevTools 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 DevTools penceresine "ilk DevTools örneği", ilk örneği denetleyen pencereye ise "ikinci DevTools örneği" adı verilecektir.
İ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.
İlk durum: İ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 uzun sürdüğü görevlerden oluşur. Bu görevlerin toplam süresi yaklaşık 10 saniye idi. Aşağıdaki ekran görüntüsünde, bu etkinlik gruplarının her birine odaklanarak neler bulunabileceğini görmek için performans paneli kullanılmaktadır.
İ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 saniye kazanıldı. 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ı.
Daha ayrıntılı incelemek için performans panelinde 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 şüpheyi gidermek için DevTools'taki Bellek panelini (performans panelindeki Bellek çekmecesinden farklı bir panel) kullanarak inceleme yaptı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 tükettiği gözlemlendi. Çağrı noktaları kontrol edildiğinde, büyük miktarlarda oluşturulan nesnelere gereksiz yere Set
türündeki özellikler atadığımız tespit edildi. Bu maliyet artıyordu ve çok fazla bellek tüketiliyordu. Uygulamanın büyük girişlerde kilitlenmesi sık karşılaşılan bir durum haline gelmişti.
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 olduğu 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 bir şey dikkatimizi çekti. Girişteki her bir veri girişi için (kodda "etkinlik" olarak bilinir) zaman çizelgesi girişlerinin dikey konumunu izleyen bir haritaya bir öğe eklendi. 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ırıyoruz.
Beşinci etkinlik grubu: Mümkün olduğunda karmaşık arama 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" (panelin üst kısmındaki zaman çizelgesi etkinliğine genel bakış) oluşturulacak verileri işleyen kısımdır. 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 mini haritayı hesaplayan işlevi doğrudan veya dolaylı olarak çağırması sonucunda çağrıldığı tespit edildi. Bunun nedeni, programın çağrı grafiğinin karmaşıklığının zaman içinde gelişmesi ve bu koda bilmeden daha fazla bağımlılık eklenmiş olmasıdır. 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şisinin karmaşıklığını biraz azaltmamız ve giriş verileri değişmeden kalırsa kodun yürütülmesini engelleyecek bir kontrol 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 geçmişten 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). Bununla birlikte, 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üzeltmeler (ve birkaç küçük düzeltme daha) uygulandıktan sonra, profil yükleme zaman çizelgesinde yapılan değişiklik aşağıdaki gibi görünüyordu:
Ö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, özellikle büyük olduğu için bizim için ilgi çekiciydi. 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 DevTools'daki Performans paneli, tarayıcıdaki yerel web profilleme aracı olduğu ve en son web platformu özellikleriyle güncel kalacak şekilde etkin bir şekilde yönetildiği için 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şileri oluşturmaktan 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 gerilemelerine yol açabilir ve kodunuzun neden bu şekilde çalıştığını anlamanızı zorlaştırarak iyileştirme yapmanızı engelleyebilir.
3. Gereksiz işleri belirleme
Eski kod tabanlarında artık ihtiyaç duyulmayan kodların bulunması 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 bu kategoriye bakmadık ancak bu kategorinin önemini vurgulamak isteriz.
8. Bonus: Ardışık düzenlerinizi karşılaştırma
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ı için kendinizi hazırlayabilirsiniz.