Mobil performans laboratuvarı
16,7 Milisaniye
60 Hz’lik bir telefonun her kareyi çizmek için 16,7 ms’si var. 10.000 kartlık bir akışı kaydır, bütçeyi kimin harcadığını gör.
Kurgulanmış bir model, cihazının ölçümü değil
60 Hz: 90 karenin 90 tanesi düştü. 60 Hz’de henüz akıcı değil.
Modellenen telefon
Kare başına bütçe: 16,7 ms
React Native: Performansa genel bakış (yeni sekmede açılır)Android Developers: Yavaş çizim (yeni sekmede açılır)web.dev: Çizim performansı (yeni sekmede açılır)
- Bahçe ekibi · 1 gKayıp eşya: mavi bir şemsiyeKart #1 · 220 tepki
- Bahçe ekibi · 2 gSabah koşusu: 5 km, her tempoya açıkKart #2 · 67 tepki
- Bina ekibi · 2 gÇatı bahçesi bu hafta sonu gönüllü arıyorKart #3 · 80 tepki
- Satranç kulübü · 10 saCumartesi sahil boyunca fotoğraf yürüyüşüKart #4 · 364 tepki
- Bahçe ekibi · 2 gÇorba akşamı: kaseni getirKart #5 · 157 tepki
- Komşular · 1 gSabah koşusu: 5 km, her tempoya açıkKart #6 · 273 tepki
- Bahçe ekibi · 36 dkKütüphane pazartesi bir saat geç açılıyorKart #7 · 308 tepki
- Bina ekibi · 1 gSalı günü bir saatliğine su kesilecekKart #8 · 390 tepki
Üretilmiş 10.000 karttan oluşan kurgusal bir akış. Akıcı kalsın diye bu sayfa onu tarayıcında her zaman sanallaştırır. Zaman çizelgesi, açtığın maliyetlerle bir telefonun ne yapacağını modeller. Akışın üstündeki şerit başlığın yerini tutar: en üstteki kartı gösterir ve başlığın thread’inde çalışır; JS’ten sürülen başlık JS meşgulken durur, liste ise kaymaya devam eder.
Kare zaman çizelgesi
TakılıyorHedef: Standart hızlı kaydırmayı (fling) önce 60 Hz’de, sonra 120 Hz’de sıfır kare kaybına indir; bunu olabildiğince az anahtar değiştirerek yap. Akış yine de bir saniyeden kısa sürede açılmalı.
60 Hz’de henüz akıcı değil.İlk taslaktan değiştirilen anahtar: 0
Bir hareket kaydetmek için telefondaki akışı kaydır (en fazla 4 saniye).
- Kullanıcının gördüğü kayıplar
- 90 / 90
- Kaçan JS kareleri
- 90
- Kaçan UI kareleri
- 32
- En kötü JS karesi
- 93,2 ms%559
- En kötü UI karesi
- 30,6 ms%184
Bütçeyi ne harcadı
Kare 1 / 90 · 0,00 snekranda düştü
JS threadBütçeyi aştı
23,2 / 16,7 ms · %139
- Kaydırma olayı ve liste penceresi1,2 ms
- Yeni satırları çiz · 1 satır1,4 ms
- Satır layout olayları · 1 satır1,6 ms
- Başlık state’i için ekranı yeniden çiz0,8 ms
- Yüklü tüm satırları yeniden çiz · 121 satır18,2 ms
UI threadBütçeyi aştı
16,70 / 16,67 ms · %100,2
- Çizim ve birleştirme2,8 ms
- Native görünümleri oluştur · 1 satır0,7 ms
- Yeni satırları ölç · 1 satır2,4 ms
- İlk çizimde fotoğrafları yükle · 1 satır10,8 ms
Bütçeyi yalnızca 0,03 ms aştı ve yine de düştü: son tarihini kaçıran kare geç gösterilmez, hiç gösterilmez. Android Developers: Yavaş çizim (yeni sekmede açılır)
Akışın açılışı: İlk kareden önce 10 satır çizilir: önce 30 ms JS, ardından 98,6 ms UI işi; toplam 129 ms.
Maliyetler
Takılıyor90 karenin 90 tanesi düştü
Her anahtar akışa bir maliyet ekler. Olabildiğince azını kapat.
ScrollView tüm alt bileşenlerini bir kerede çizer. Açıldıktan sonra kaydırmak ucuzdur, ama açılış 10.000 satıra mal olur ve üst bileşenin her yeniden çizimi hepsine ulaşır. FlatList satırları yaklaştıkça çizer, ekrandan çok uzaklaşanları kaldırır.
React Native: ScrollView (yeni sekmede açılır)React Native: FlatList yapılandırmasını iyileştirme (yeni sekmede açılır)
Taslak FlatList’e satır içi bir renderItem veriyor; ekran her yeniden çizildiğinde FlatList yeni prop’lar alır ve yüklü her satır yeniden çizilir. FlatList bir PureComponent’tir: prop’lar sabit kalsaydı güncellemeyi atlardı. React.memo bir satırı yalnızca kendi prop’ları değiştiğinde yeniden çizer; karşılaştırmanın kendisi yine de satır başına biraz maliyetlidir.
React Native: FlatList (yeni sekmede açılır)React Native: FlatList yapılandırmasını iyileştirme (yeni sekmede açılır)
Piksel başına 4 baytla çözülen bir fotoğraf, kart boyutundaki 1 MB yerine yaklaşık 45,8 MB yer kaplar ve ilk çiziminde tamamı GPU’ya yüklenir. Kutudan büyük olmayan bir küçük resim gönder.
Android Developers: Büyük bitmap’leri verimli yükleme (yeni sekmede açılır)Android Developers: Yavaş çizim (yeni sekmede açılır)React Native: FlatList yapılandırmasını iyileştirme (yeni sekmede açılır)
Sonraki sayfa hızlı kaydırmanın ortasında gelir. Model, 2.000 kartın tek seferde JSON.parse edilmesi için 70 ms hesaplar: 60 Hz’de dört kareden uzun, bölünmeyen tek bir iş. web.dev, 50 ms’yi aşan her işi uzun görev sayar. Modelde 50 kartlık sayfalar (2,5 ms) her ayrıştırmayı tek bir karenin içinde tutar.
web.dev: Uzun görevleri iyileştirme (yeni sekmede açılır)React Native: Performansa genel bakış (yeni sekmede açılır)
Her kaydırma olayı state’i günceller; ekran her karede JS thread’inde yeniden çizilir ve bu thread meşgul olduğunda başlık donar. Native driver ile (height değil transform canlandırılarak) başlık UI thread’inde çalışır ve tıkanan bir JS thread’i artık görünmez.
React Native: Performansa genel bakış (yeni sekmede açılır)React Native: Animasyonlar, native driver kullanımı (yeni sekmede açılır)
Liste her yeni satırın yüksekliğini bir yerleşim geçişinden ve JS thread’ine geri gönderilen bir layout olayından öğrenir. Bu, okumaların döngü içinde senkron yerleşim hesabını zorladığı web’deki layout thrashing değildir: liste ölçümü asenkron yapar. Yine de her yeni satır için bir yerleşim işidir; getItemLayout ile sabit yükseklikli satırlar bu işi atlar.
React Native: Performansa genel bakış (yeni sekmede açılır)React Native: FlatList yapılandırmasını iyileştirme (yeni sekmede açılır)web.dev: Büyük, karmaşık yerleşimlerden ve layout thrashing’den kaçının (yeni sekmede açılır)
Model ve sınırları
- Her maliyet, orta segment bir telefon için kurgulanmış ve sorunun biçimini göstermek için seçilmiş bir sabittir. Hiçbiri ölçüm değildir. Android’de olduğu gibi bütçeyi ne kadar az aşarsa aşsın kare düşmüş sayılır; bu yüzden bazı kareler çizginin hemen üstünde kalır.
- Her şerit işe bir vsync’te başlar. Bir sonraki vsync’te hâlâ meşgul olan şerit o kareyi atlar; girdisi birleşerek bir sonraki boş kareyi bekler.
- Kullanıcı bir kaybı iki durumda görür: UI thread bir kareyi kaçırdığında bütün ekran durur; başlık JS’ten canlandırılırken JS thread bir kareyi kaçırdığında ise başlık durur, liste kaymaya devam eder. Native kaydırma JS’i beklemez.
- Satırlar FlatList penceresine (varsayılan olarak 21 ekran) girdikçe çizilir; pencereyi geçip giden satırlar hiç çizilmez. Gerçek liste her partide en fazla 10 yeni satır çizer (maxToRenderPerBatch) ve partiler arasında 50 ms bekler (updateCellsBatchingPeriod). Model bu hızı karelere yayar ve bir seferde en fazla bir tam parti çizer. Bundan hızlı kaydırırsan, FlatList’te olduğu gibi ekranda boş satırlar görünür.
- Görsel maliyeti, kart ekranda içeriğiyle ilk kez çizildiğinde hesaba katılır; büyük bir bitmap’i GPU’ya yüklemek birkaç milisaniye sürebilir. Boş bir satır dolana kadar maliyet getirmez. Kod çözme hesaba katılmaz; model bunun arka plandaki bir thread’de yapıldığını varsayar.
- Modellenmeyenler: ağ süresi, çöp toplama, eski köprü ile JSI farkı, ısıl kısıtlama ve bellek sınırları.
Kaynaklar
- React Native: Performansa genel bakış (yeni sekmede açılır)
- web.dev: Çizim performansı (yeni sekmede açılır)
- Android Developers: Yavaş çizim (yeni sekmede açılır)
- React Native: FlatList yapılandırmasını iyileştirme (yeni sekmede açılır)
- React Native: FlatList (yeni sekmede açılır)
- React Native: ScrollView (yeni sekmede açılır)
- React Native: Animasyonlar, native driver kullanımı (yeni sekmede açılır)
- web.dev: Uzun görevleri iyileştirme (yeni sekmede açılır)
- web.dev: Büyük, karmaşık yerleşimlerden ve layout thrashing’den kaçının (yeni sekmede açılır)
- Android Developers: Büyük bitmap’leri verimli yükleme (yeni sekmede açılır)
- React Native: Yeni Mimari hakkında (yeni sekmede açılır)
Bu laboratuvar neden var
Umut, Pometop mobil uygulaması, Ritmiva ve sosyal seyahat uygulaması Bumble gibi React Native ve Expo uygulamaları geliştiriyor. Bu laboratuvar, bu tür uygulamalardaki her ekranın sığması gereken kare bütçesini gösteriyor. Sayılar o uygulamalardan alınmış ölçümler değil.