<

Flutter'da Performans Profilleme: DevTools ile Saha Rehberi

Şubat başında bir müşterimizin sipariş toplama uygulaması için saha ekibinden gelen şikâyet tek cümleydi: "Uygulama takılıyor." Ofisteki test cihazlarında — hepsi orta-üst segment — her şey ipek gibiydi. Sahaya gidip depo personelinin elindeki üç yaşındaki, 3 GB RAM'li Android cihazı aldığımızda tablo değişti: ürün listesinde kaydırma sırasında kare hızı 60'tan 22'ye düşüyor, sepete ekleme animasyonu ise resmen slayt gösterisine dönüyordu. O gün bir kez daha teyit ettik: Flutter performansı masa başında değil, müşterinizin en kötü cihazında ölçülür.

Bu yazı, o projede ve benzer onlarcasında oturttuğumuz profilleme rutinimizin özeti. DevTools dokümantasyonunu tekrar anlatmayacağım; sahada hangi sırayla nereye baktığımızı anlatacağım.

Debug modda profilleme yapan kendine iftira eder

En sık gördüğümüz hata bu, o yüzden en başa yazıyorum. Debug build, JIT ile çalışır, assert'ler açıktır, performansı gerçek uygulamanın iki-üç kat altındadır. Debug modda "uygulamam yavaş" demek, ayağınızda kartopu kıyafetiyle koşu süresi ölçmeye benzer. Bizim ekipte kural net: performans şikâyeti geldiğinde ilk iş profile mode build alıp gerçek cihaza kurmak. Emülatör de yasak; GPU davranışı, termal kısıtlama, disk hızı — hiçbiri emülatörde gerçeği yansıtmıyor.

Profile mode'un debug'a göre tek eksiği, hata ayıklayıcının konforundan vazgeçmeniz; ama zaten performans avındayken breakpoint değil, zaman çizelgesi okuyorsunuz. DevTools profile build'e de bağlanıyor ve ihtiyacınız olan her sekme — Performance, CPU Profiler, Memory — orada çalışıyor.

İkinci adım, cihaz seçimi. Müşterinin kullanıcı analitiğinden en yaygın üç düşük segment cihazı çıkarıyoruz ve ofiste fiziksel olarak bulunduruyoruz. Kulağa masraf gibi geliyor; toplam maliyeti bir günlük hata ayıklama mesaisinden ucuz.

Jank'in kaynağını bulmak: UI thread mi, raster thread mi?

DevTools'un Performance sekmesini açtığınızda göreceğiniz ilk şey frame çubukları. Burada kritik ayrım şu: takılan frame'in zamanı UI thread'de mi harcanıyor, raster thread'de mi? Bu ayrım, sonraki bir saatinizi nereye harcayacağınızı belirliyor, çünkü iki sorunun çözüm dünyaları tamamen ayrı.

UI thread şişkinse sorun Dart kodunuzda: build metodunda ağır hesap, gereksiz rebuild'ler, senkron JSON parse. Bizim depo uygulamasında ilk suçlu buydu — ürün listesindeki her kart, her scroll frame'inde yeniden build oluyordu çünkü listeyi saran bir üst widget, her konum güncellemesinde (saniyede beş kez geliyordu) tüm alt ağacı tetikliyordu. DevTools'ta "Track widget rebuilds" seçeneğini açınca tablo netleşti: tek scroll'da 340 kart build'i. Provider yapısını bölüp konum verisini sadece ilgilenen widget'a select ile dinlettiğimizde sayı 12'ye indi.

Raster thread şişkinse sorun çizim tarafında: pahalı gölgeler, saydamlık katmanları, clip operasyonları, dev resimler. Aynı projede ikinci suçlu buradaydı. Tasarımdan gelen kart gölgesi her kartta ayrı bir saveLayer tetikliyordu; kaydırma sırasında GPU, ekrandaki yirmi kartın her biri için katman birleştirme yapıyordu. Gölgeyi önceden render edilmiş dokuz parçalı bir PNG ile değiştirdik. Gözle ayırt edilemiyor; raster süresi frame başına 11 ms'den 4 ms'ye indi.

Tek cümlelik özet geçeyim: UI thread sorunu kod okuyarak, raster sorunu ekran okuyarak çözülür.

Shader derleme takılmaları ve Impeller sonrası hayat

Eski Flutter projelerinden hatırlarsınız: bir animasyon ilk oynatılışta takılır, sonrakilerde akıcıdır. Bu klasik shader derleme jank'iydi. Impeller'ın Android tarafında da varsayılan hâle gelmesiyle bu dert büyük ölçüde tarihe karıştı; shader'lar artık çalışma zamanında değil derleme zamanında hazırlanıyor. Ama "büyük ölçüde" diyorum çünkü sahada hâlâ iki durumda benzer takılmalar görüyoruz: eski Flutter sürümünde kalmış projeler (devraldığımız projelerin yarısı böyle) ve platform view içeren ekranlar — harita, webview, reklam bileşeni.

Platform view konusu araç takip uygulamalarımızda kanayan yara. Harita widget'ının üstüne Flutter katmanı bindirdiğinizde kompozisyon maliyeti ciddi artıyor. Çözümümüz genelde mimari: harita ekranında Flutter tarafındaki animasyonları asgariye indirmek, araç ikonlarının güncellemesini harita SDK'sının kendi katmanına bırakmak. Flutter her işi yapabilir; her işi yapması gerektiği anlamına gelmiyor.

Bellek grafiği yalan söylemez, ama geç söyler

Performans şikâyetlerinin sinsi bir alt türü var: uygulama ilk on dakika akıcı, sonra yavaşlıyor. Bu desende neredeyse her zaman bellek büyümesi vardır ve DevTools'un Memory sekmesi burada devreye giriyor. Depo uygulamasında bunu da yaşadık: her sipariş detayına giriş, tam çözünürlüklü ürün fotoğraflarını önbelleğe alıyor ve hiç bırakmıyordu. İki yüz sipariş sonra imaj önbelleği 900 MB'a dayanmış, sistem GC baskısı altında sürekli duraksıyordu. Çözüm iki satır: cacheWidth ile fotoğrafları görüntülenen boyutta decode etmek ve önbelleğe üst sınır koymak.

Snapshot karşılaştırma özelliğini özellikle öneririm: bir ekrana girip çıkın, snapshot alın, aynı işlemi on kez tekrarlayıp yeni snapshot alın. Fark listesinde büyüyen ne varsa sızıntınız odur. Beş dakikalık bu ritüel, bize kim bilir kaç haftalık "ara sıra oluyor, nedenini bilmiyoruz" ticketı kazandırdı.

Ölçmeden kapatılan ticket, geri açılan tickettır

Sahadaki cihaza her zaman DevTools bağlayamazsınız; bunun için de bir yedek planımız var. Kritik akışlara Timeline event'leri ekliyoruz ve uygulamanın içine, yalnızca dahili kullanıcılarda açılan bir frame süresi kaydedici gömüyoruz. Depo personelinin cihazında yaşanan bir takılma, ertesi sabah elimizde zaman damgalı bir ölçüm dosyası olarak duruyor. "Bende olmuyor" cümlesi, ölçüm dosyası varken tartışma konusu olmaktan çıkıyor. Aynı veriyi sürüm bazında da karşılaştırıyoruz; yeni sürüm ortalama frame süresini yüzde onun üzerinde kötüleştirdiyse yayın durur, sebep bulunmadan devam edilmez. Bu eşiği koyduğumuzdan beri "yavaşlamış ama ne zaman yavaşladı bilmiyoruz" konuşması hiç yaşanmadı; suçlu sürüm, grafikte kendini gösteriyor.

Profillemenin ekip kültürüne dokunan bir tarafı var. "Optimizasyon yaptım, daha hızlı hissettiriyor" cümlesi bizim ekipte kabul görmüyor; önce-sonra frame süresi grafiği olmayan performans PR'ı merge edilmiyor. Bu disiplin abartılı gelebilir ama iki sebebi var. Birincisi, hissiyat aldatıcı — kendi yazdığınız optimizasyonun hızlı hissettirmesi, deneyimin değil umudun ölçümüdür. İkincisi, sayılar müşteri iletişimini dönüştürüyor. "Uygulamanız artık daha akıcı" demek yerine "kaydırma sırasında kare süresi 45 ms'den 9 ms'ye indi, yani en yavaş cihazda bile 60 fps'nin altına düşmüyoruz" diyorsunuz. Depo projesinde bu raporu sunduğumuzda müşterinin operasyon müdürünün cevabı şuydu: "İlk defa bir yazılımcının ne yaptığını anladım."

Saha ekibinden gelen "takılıyor" şikâyetiyle başlayan o iş, üç haftada kapandı ve uygulamanın mağaza puanı iki ayda 3,4'ten 4,3'e çıktı. Sihir yok; profile mode, doğru cihaz, DevTools ve ölçüm disiplini var. Elinizde benzer bir "yavaş ama neden bilmiyoruz" uygulaması varsa bize yazın — çoğu vakada ilk günün sonunda suçlunun UI thread mi raster thread mi olduğunu söyleyebiliyoruz, gerisi zanaat.

📅 Yayınlanma:  ·  Yakup Zengin