Geliştirmekte olduğunuz uygulama türü ne olursa olsun, uygulamanın performansını optimize etmek, hızlı yüklenmesini sağlamak ve sorunsuz etkileşimler sunmak, kullanıcı deneyimi ve uygulamanın başarısı için kritik öneme sahiptir. Bunu yapmanın bir yolu, bir uygulama zaman aralığında çalışırken arka planda neler olup bittiğini görmek için profil oluşturma araçlarını kullanarak uygulama etkinliğini incelemektir. Geliştirici Araçları'ndaki Performans paneli, web uygulamalarının performansını analiz ve optimize etmek için ideal bir profil oluşturma aracıdır. Uygulamanız Chrome'da çalışıyorsa, uygulamanız çalıştırılırken tarayıcının ne yaptığına ilişkin ayrıntılı bir görsel genel bakış sunar. Bu etkinliği anlamak, performansı artırmak için uygulayabileceğiniz kalıpları, performans sorunlarını ve performans noktalarını belirlemenize yardımcı olabilir.
Aşağıdaki örnek, Performans panelini kullanma konusunda size yol gösterir.
Profil oluşturma senaryomuzu oluşturma ve yeniden oluşturma
Kısa bir süre önce, Performans panelinin daha iyi performans göstermesini sağlamak için bir hedef belirledik. Özellikle, büyük hacimli performans verilerini daha hızlı yüklemesini istedik. Örneğin, uzun süreli veya karmaşık işlemlerin profilini çıkarırken ya da yüksek ayrıntı düzeyine sahip verileri yakalarken böyle bir durum söz konusudur. Bunun için uygulamanın nasıl performans gösterdiğini ve bu şekilde neden performans gösterdiğini anlamak gerekiyordu. Bu da bir profil oluşturma aracı kullanılarak gerçekleştirildi.
Bildiğiniz gibi DevTools bir web uygulamasıdır. Bu nedenle, Performans paneli kullanılarak profili oluşturulabilir. Bu panelin profilini oluşturmak için Geliştirici Araçları'nı, ardından ona ekli başka bir Geliştirici Araçları örneğini açabilirsiniz. Google'da bu kurulum, DevTools-on-DevTools 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.
İkinci DevTools örneğinde, bundan sonra performans paneli olarak adlandırılacak olan Performans paneli, ilk DevTools örneğini gözlemleyerek bir profil yükleyen senaryoyu yeniden oluşturur.
İkinci Geliştirici Araçları örneğinde canlı kayıt başlatılırken ilk örnekte ise diskteki bir dosyadan bir profil yüklenir. Büyük girişlerin işlenmesinin performansını doğru bir şekilde belirlemek için büyük bir dosya yüklenir. Her iki örneğin de yüklenmesi bittiğinde, genellikle trace adı verilen performans profili oluşturma verileri, bir profil yükleyen perf panelinin ikinci Geliştirici Araçları örneğinde görünür.
Başlangıçtaki durum: iyileştirme fırsatlarını belirleme
Yükleme tamamlandıktan sonra, bir sonraki ekran görüntüsünde ikinci perf paneli örneğimizde aşağıdakiler gözlemlendi. Ana etiketli parçanı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ülüyor. 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, nelerin bulunabileceğini görmek amacıyla bu etkinlik gruplarının her birine odaklanmak için kullanılmıştır.
İlk etkinlik grubu: gereksiz iş
İlk etkinlik grubunun hâlâ çalışan, ancak aslında gerekli olmayan eski kod olduğu anlaşıldı. Özetle, processThreadEvents
etiketli yeşil blokun altındaki her şey boşa çaba harcanırdı. Bu sonuç hemen geldi. İşlev çağrısının kaldırılması yaklaşık 1,5 saniye tasarruf sağladı. Güzel!
İkinci etkinlik grubu
İkinci etkinlik grubunda çözüm ilki kadar basit değildi. buildProfileCalls
işlemi yaklaşık 0, 5 saniye sürdü ve bu işlemden kaçınılabilecek bir durum değildi.
Daha ayrıntılı araştırma yapmak için merak nedeniyle perf 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ılırken mavi çizgi grafiğin aniden nasıl değiştiğini görebilirsiniz. Bu, olası bir bellek sızıntısına işaret eder.
Bu şüpheyle ilgili araştırma yapmak için Bellek panelini (DevTools'daki başka bir panel ve perf panelindeki Bellek çekmecesinden farklı) kullandık. Bellek panelinde, CPU profilini yükleyen perf panelinin yığın anlık görüntüsünü kaydeden "Ayırma örneklemesi" profil oluşturma 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şturulan nesnelere gereksiz yere Set
türünde özellikler atadığımız tespit edildi. Bu maliyetler artmakta ve uygulamanın büyük girişlerde kilitlenmesi yaygın görülen bir noktaya kadar büyük oranda bellek harcanıyordu.
Kümeler, benzersiz öğeler depolamak ve veri kümelerinin tekilleştirilmesi ve daha etkili aramalar sağlamak gibi yöntemlerle içeriklerinin benzersizliğini kullanan işlemler sunmak için yararlıdır. Ancak depolanan verilerin kaynaktan benzersiz olacağı garanti edildiği için bu özellikler gerekli değildi. Bu nedenle, en başta setler gerekli değildi. Bellek ayırmayı iyileştirmek için Set
olan özellik 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 tahsisinin azaldığı gözlemlendi. Bu değişiklik sayesinde hızda önemli düzeyde iyileşme sağlanamasa da uygulamanın faydası, uygulamanın daha seyrek kilitlenmesiydi.
Üçüncü etkinlik grubu: veri yapısında ters ibrazların değerlendirilmesi
Üçüncü bölüm kendine özgüdür: flame grafiğinde, bu bölümün derin işlev çağrılarını ve bu örnekte derin yinelemeleri ifade eden dar ama uzun sütunlardan oluştuğunu görebilirsiniz. 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şılmaktadır: appendEventAtLevel
, bu da bunun bir performans sorunu olabileceğini gösteriyordu
appendEventAtLevel
işlevinin uygulanmasında bir şey öne çıktı. Girişteki (kodda "etkinlik" olarak bilinir) her bir veri girişi için, zaman çizelgesi girişlerinin dikey konumunu izleyen bir haritaya bir öğe eklenmiştir. Depolanan öğe miktarı çok fazla olduğundan bu durum sorunluydu. Haritalar, temel tabanlı aramalar için hızlı olsa da bu avantaj ücretsiz değildir. Örneğin, bir harita büyüdükçe, yeniden karma oluşturma işlemi nedeniyle haritaya veri eklemek pahalı hale gelebilir. Bu maliyet, haritaya arka arkaya büyük miktarlarda öğe eklendiğinde belirgin 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);
// ...
}
Alev grafiğindeki her giriş için haritaya bir öğe eklememizi gerektirmeyen başka bir yaklaşım denedik. Önemli düzeyde iyileştirme yapıldı. Bu durum, performans sorununun gerçekten de tüm verilerin haritaya eklenmesinden kaynaklanan ek yük ile ilgili olduğunu kanıtladı. Etkinlik grubunun küçülme süresi yaklaşık 1,4 saniyeden yaklaşık 200 milisaniyeye çıkar.
Önce:
Sonra:
Dördüncü etkinlik grubu: Yinelenen çalışmaları önlemek için kritik olmayan işlerin ve önbellek verilerinin ertelenmesi
Bu pencereyi yakınlaştırdığınızda, neredeyse aynı iki işlev çağrısı bloğu olduğu görülebilir. Adlandırılan işlevlerin adına bakarak, bu blokların ağaç oluşturan koddan (örneğin, refreshTree
veya buildChildren
gibi adlarla) oluştuğu çıkarımında bulunabilirsiniz. Aslında, ilgili kod panelin alt çekmecesinde ağaç görünümlerini oluşturan koddur. Buradaki ilginç 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ü ("Aşağıdan yukarıya", "Çağrı Ağacı" ve "Etkinlik Günlüğü" sekmeleri) seçmesi gerekir. Ayrıca, ekran görüntüsünden de anlayabileceğ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 çıktılarına ihtiyaç duymazlar. Bu nedenle, görev, profilin yüklenmesi açısından 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ğaç hesaplamasını, kullanıcının ağaç görünümünü manuel olarak açtığı zamana erteledik. Ancak bu durumda bu ağaçların yapılması için gereken ücreti ödemeye değer. Bu aracın iki kez çalıştırıldığı toplam süre yaklaşık 3,4 saniyeydi.Bu nedenle, aracın ertelenmesi, yükleme süresinde önemli bir fark yarattı. Bu tür görevlerin önbelleğe alınmasıyla ilgili çalışmalarımız devam ediyor.
Beşinci etkinlik grubu: Mümkün olduğunda karmaşık çağrı hiyerarşilerinden kaçının
Bu grup ayrıntılı olarak incelendiğinde, belirli bir çağrı zincirinin tekrar tekrar çağrıldığı açıkça anlaşılmıştır. Aynı kalıp, alev grafiğinin farklı yerlerinde 6 kez göründü ve bu aralığın toplam süresi yaklaşık 2,4 saniye oldu.
Birden çok kez çağrılan ilgili kod, "mini harita"da (panelin üst kısmındaki zaman çizelgesi etkinliğine genel bakış) oluşturulacak verileri işleyen kısımdır. Neden birden fazla kez tekrarlandığını anlayamadım ancak en azından 6 kez olması gerekmiyordu. 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, yükleme hattındaki birden fazla parçanın doğrudan veya dolaylı olarak mini haritayı hesaplayan işlevi çağırması sonucunda, ilgili kodun çağrıldığı tespit edildi. Bunun nedeni, programın çağrı grafiğinin karmaşıklığının zamanla gelişmiş ve farkında olmadan bu koda daha fazla bağımlılık eklenmesidir. Bu sorunun hızlı bir çözümü yoktur. Çözümün yolu, ilgili kod tabanının mimarisine bağlıdır. Örneğimizde, çağrı hiyerarşisi karmaşıklığını biraz azaltmamız ve giriş verileri değişmeden kaldığı takdirde kodun yürütülmesini önlemek için bir denetim eklememiz gerekti. Bunu uyguladıktan sonra, zaman çizelgesini şu şekilde özetledik:
Mini harita oluşturma işleminin bir kez değil, iki kez gerçekleştirildiğini unutmayın. Bunun nedeni, her profil için çizilen iki mini harita olmasıdır: Biri panelin üst kısmındaki genel bakış için, diğeri ise o anda görünür olan profili geçmişten seçen açılır menü içindir (bu menüdeki her öğe, seçtiği profile ilişkin bir genel bakış içerir). Bununla birlikte, bu ikisi tamamen aynı içeriğe sahip olduğundan biri diğeri için yeniden kullanılabilmelidir.
Bu mini haritaların her ikisi de tuval üzerine çizilmiş resimler olduğundan, fazladan zaman kazanmak için drawImage
tuval yardımcı programını kullanmak ve ardından kodu yalnızca bir kez çalıştırmak gerekti. Bu çabanın bir sonucu olarak, grubun süresi 2,4 saniyeden 140 milisaniyeye düşürüldü.
Sonuç
Tüm bu düzeltmelerin (ve başka birkaç küçük düzeltme) uygulanmasının ardından, profil yükleme zaman çizelgesindeki değişiklik aşağıdaki gibi oldu:
Önce:
Sonra:
İyileştirmelerden sonraki yükleme süresi 2 saniyeydi. İşlemlerin çoğu hızlı düzeltmelerden oluştuğu için nispeten az çabayla yaklaşık%80 iyileştirme gerçekleştirildi. Elbette başlangıçta nenin yapılması gerektiğini doğru şekilde tanımlamak çok önemliydi ve performans paneli bunun için doğru araçtı.
Bu sayıların, çalışmanın konusu olarak kullanılan bir profile özgü olduğunu vurgulamak da önemlidir. Profil, oldukça 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şme, performans panelinde yüklenen her profil için geçerlidir.
Çalmalar
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 faydalanın
Profil oluşturma araçları, uygulamanız çalışırken uygulamanızda neler olduğunu anlamak, özellikle de performansı artırma fırsatlarını belirlemek açısından çok yararlıdır. Chrome Geliştirici Araçları'ndaki Performans paneli, tarayıcının yerel web profili oluşturma aracı olduğundan ve en son web platformu özellikleriyle güncel kalmak üzere etkin bir şekilde korunduğundan web uygulamaları için mükemmel bir seçenektir. Ayrıca, artık önemli ölçüde daha hızlı! 😉
Temsili iş yükleri olarak kullanılabilecek örnekler kullanın ve neler bulabileceğinizi görün.
2. Karmaşık çağrı hiyerarşilerinden kaçınma
Mümkün olduğu durumlarda çağrı grafiğinizi çok karmaşık hale getirmekten kaçının. Karmaşık çağrı hiyerarşileri ile performans regresyonlarını eklemek kolaydır ve kodunuzun neden bu şekilde çalıştığını anlamak zordur. Bu da geliştirmelerin yapılmasını zorlaştırır.
3. Gereksiz çalışmaları belirleme
Eskiyen kod tabanlarının artık gerekli olmayan kodları içermesi yaygın bir durumdur. Örneğimizde, eski ve gereksiz kodlar toplam yükleme süresinin önemli bir kısmını alıyordu. Bunu kaldırmak, en kolay hedefti.
4. Veri yapılarını uygun şekilde kullanın
Performansı optimize etmek için veri yapılarını kullanın. Ayrıca, hangilerini kullanacağınıza karar verirken her bir veri yapısı türünün getirdiği maliyetleri ve dengeleri de anlayın. Bu durum yalnızca veri yapısının mekandaki karmaşıklığı değil, aynı zamanda uygulanabilir işlemlerin zaman karmaşıklığı da değildir.
5. Karmaşık veya tekrarlanan işlemlerde yinelenen çalışmaları önlemek için sonuçları önbelleğe alın
İşlemin yürütülmesi maliyetliyse sonuçları bir dahaki sefere ihtiyaç duyulması için depolamak mantıklıdır. İşlem birçok kez yapılıyorsa, tek tek işlem çok maliyetli olmasa bile bunu yapmak da 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 işlemi kritik yolu genişletiyorsa, çıktısı gerçekten gerekli olduğunda geç çağrısı yaparak görevi ertelemeyi düşünebilirsiniz.
7. Büyük girişler için verimli algoritmalar kullanın
Büyük girişler için optimum zaman karmaşıklığı algoritmaları kritik hale gelir. Bu örnekte bu kategoriyi ele almadık. Ancak, önemi neredeyse hafife alınabilir.
8. Bonus: Satış kanallarınızı karşılaştırın
Gelişen kodunuzun hızlı kalmasını sağlamak için davranışı izlemek ve standartlarla karşılaştırmak akıllıca olur. Bu sayede, regresyonları proaktif olarak tespit edip genel güvenilirliği artırır ve uzun vadeli başarı elde edersiniz.