4-qism · Sifat va ishlab chiqarish·10-bob
Performance optimizatsiya va xotira boshqaruvi
Flutter ilovasini tez va silliq qilish uchun bilishingiz kerak bo'lgan barcha texnikalar — const konstruktorlardan tortib DevTools profiling va xotira sizmalarigacha, intervyu savollari bilan.
Senior Flutter dasturchidan kutiladigan asosiy narsalardan biri — nafaqat ishlaydigan kod yozish, balki nega sekinlik yuz berayotganini tushuntira olish va uni o'lchab, tuzata olish. Intervyularda ko'pincha "ilovangiz sekinlashsa, birinchi nima qilasiz?" tarzidagi savollar beriladi — javob har doim "taxmin qilish emas, profillash" bo'lishi kerak.
1. Nega performance muammolari paydo bo'ladi#
Flutter har bir freymni (frame) 16.6 millisekundda (60 FPS uchun) yoki 8.3
millisekundda (120 FPS uchun) chizishi kerak. Agar build(), layout() yoki
paint() bosqichlaridan biri bu vaqtdan oshib ketsa, freym tushib qoladi —
foydalanuvchi buni "jank" (sakrash, qotish) sifatida his qiladi.
Asosiy sabablar:
- Keraksiz rebuild'lar — widget qayta qurilmasligi kerak bo'lgan holatda ham qayta quriladi.
- Og'ir
build()metodlari — build ichida qimmat hisob-kitob yoki sinxron I/O. - Katta ro'yxatlarni noto'g'ri render qilish — barcha elementlarni bir vaqtda qurish.
- Xotira sizmalari (memory leaks) — dispose qilinmagan controller/subscription'lar sekin-asta xotirani band qiladi va GC (garbage collector) yukini oshiradi.
2. const konstruktorlar — eng arzon optimizatsiya#
const widget — Dart tomonidan compile vaqtida yaratiladigan, o'zgarmas
obyekt. Flutter widgetni diffing qilishda, agar ikkala tomonda ham xuddi shu
const instance bo'lsa, uni hatto solishtirmasdan o'tkazib yuboradi.
// Har safar rebuild bo'lganda yangi obyekt yaratiladi — chuqurroq solishtirish kerak
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(16),
child: Text('Salom'),
);
}
// const bilan — Flutter bu subtree'ni umuman qayta ko'rib chiqmaydi
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.all(16),
child: Text('Salom'),
);
}3. Rebuild'larni cheklash — "qayerda o'zgarsa, o'sha yer qursin"#
Eng katta xato — butun ekranni bitta katta StatefulWidget ichiga joylab,
kichik o'zgarish uchun butun daraxtni qayta qurish. To'g'ri yondashuv —
o'zgaruvchi qismni kichik, alohida widgetga ajratish.
// YOMON: har safar counter o'zgarsa, butun Column qayta quriladi
class BadCounter extends StatefulWidget {
@override
State<BadCounter> createState() => _BadCounterState();
}
class _BadCounterState extends State<BadCounter> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
const ExpensiveHeader(), // const bo'lsa ham, parent qayta build bo'lganda chaqiriladi
Text('$_count'),
const ExpensiveFooter(),
],
);
}
}
// YAXSHI: faqat raqamni ko'rsatuvchi qism alohida widget
class GoodCounter extends StatelessWidget {
const GoodCounter({super.key});
@override
Widget build(BuildContext context) {
return const Column(
children: [
ExpensiveHeader(),
_CounterText(),
ExpensiveFooter(),
],
);
}
}State management kutubxonalarida bu tamoyil Selector (Provider),
context.select (Riverpod), BlocSelector (Bloc) ko'rinishida ifodalanadi —
ular faqat kerakli maydon o'zgarganda rebuild qiladi, butun obyekt emas.
Batafsil [[07-state-management]] bobida.
ValueListenableBuilder ham xuddi shu maqsadda ishlaydi — faqat u o'rab turgan
builder funksiyasini qayta chaqiradi, tashqi widget'lar tegilmaydi:
final ValueNotifier<int> counter = ValueNotifier(0);
ValueListenableBuilder<int>(
valueListenable: counter,
builder: (context, value, child) {
return Text('$value'); // faqat shu Text qayta quriladi
},
)4. Katta ro'yxatlar — ListView.builder va lazy rendering#
ListView(children: [...]) barcha elementlarni darhol quradi — 1000 ta element
bo'lsa, 1000 tasi ham xotiraga va render pipeline'ga tushadi. ListView.builder
esa faqat ekranda ko'rinadigan (va biroz atrofidagi) elementlarni quradi:
// YOMON — 10 000 ta elementning hammasi darhol quriladi
ListView(
children: items.map((item) => ItemTile(item: item)).toList(),
)
// YAXSHI — lazy, faqat ko'rinadigan qism quriladi
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemTile(item: items[index]),
)Juda uzun, murakkab scroll tarkibi (masalan, header + grid + list bitta
sahifada) uchun CustomScrollView + Sliver oilasi ishlatiladi — bu haqda
[[06-layout-va-rendering]] bobida batafsil yozilgan.
- itemCount
- intRo'yxat uzunligi — Flutter scrollbar hajmini shundan hisoblaydi.
- itemBuilder
- (context, index) => WidgetFaqat kerakli index uchun chaqiriladi, natija keshlanadi va ekrandan chiqqanda tashlanadi.
- cacheExtent
- doubleEkrandan tashqarida oldindan qurilishi kerak bo'lgan piksel maydoni — scroll silliqligini oshiradi.
5. Rasm (image) xotira sarfi#
Rasm dekodlash — eng ko'p xotira yeydigan operatsiyalardan biri. 4000x3000 piksel JPEG fayl diskda 2 MB bo'lishi mumkin, lekin dekodlanganda xotirada ~48 MB (4000 × 3000 × 4 bayt/piksel) egallaydi — chunki ekranga chiqarish uchun u siqilmagan piksel massiviga aylantiriladi.
// YOMON — original o'lchamda dekodlanadi, 80x80 piksel joyga chiqarilsa ham
Image.network('https://example.com/katta-rasm.jpg')
// YAXSHI — dekodlash o'lchami cheklanadi, xotira sezilarli kamayadi
Image.network(
'https://example.com/katta-rasm.jpg',
cacheWidth: 160, // fizik piksel (DPR hisobga olingan holda)
)cached_network_image kabi kutubxonalar disk-level keshlash qo'shadi — bir xil
rasm qayta yuklanmaydi, lekin cacheWidth/cacheHeight bilan dekodlash
o'lchamini cheklash alohida, mustaqil optimizatsiya.
6. RepaintBoundary — qayta chizishni izolyatsiya qilish#
Flutter render daraxtini "layer"larga bo'ladi. Agar bitta widget tez-tez
o'zgarsa (masalan, animatsiya), lekin uning atrofidagi statik kontent katta
bo'lsa, RepaintBoundary yo'qligida butun subtree qayta chiziladi.
Column(
children: [
const HugeStaticImage(), // qayta chizilishi shart emas
RepaintBoundary(
child: const PulsingDot(), // faqat shu qism o'z layerida qayta chiziladi
),
],
)DevTools'dagi "Highlight Repaints" rejimi yordamida qaysi widget'lar kutilganidan ko'proq qayta chizilayotganini vizual ko'rish mumkin.
7. Flutter DevTools — profiling qurollari#
Senior intervyuda "ilovani qanday profillaysiz" degan savolga aniq qurol nomlarini aytish kutiladi:
Performance / Timeline view
Har bir freymning qancha vaqt olganini ko'rsatadi. Qizil chiziqdan yuqori turuvchi freymlar — jank. "UI" va "Raster" thread'lari alohida ko'rsatiladi: UI thread
build/layoutni, Raster thread GPU'ga chizishni bajaradi.Widget rebuild profiler
Qaysi widget qancha marta qayta qurilganini ko'rsatadi — kutilmagan tez-tez rebuild'larni topish uchun birinchi qadam.
Memory tab
Heap hajmi vaqt bo'yicha qanday o'sishini ko'rsatadi. Doim o'suvchi, hech qachon pasaymaydigan grafik — xotira sizmasining belgisi.
CPU Profiler
Qaysi funksiya qancha CPU vaqti yeganini "flame chart" ko'rinishida ko'rsatadi — og'ir sinxron hisob-kitoblarni topish uchun.
8. Xotira sizmalari (memory leaks) — eng keng tarqalgan sabablar#
Dart'da avtomatik GC bor, lekin agar obyekt biror joyda hali ham
"tinglanayotgan" bo'lsa (masalan, dispose qilinmagan StreamSubscription),
GC uni tashlab yubora olmaydi — chunki texnik jihatdan u hali "ishlatilmoqda".
class _MyPageState extends State<MyPage> {
late final AnimationController _controller;
late final StreamSubscription _sub;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(seconds: 1));
_sub = someStream.listen((_) {});
}
@override
void dispose() {
_controller.dispose(); // yo'q bo'lsa — AnimationController "osilib qoladi"
_sub.cancel(); // yo'q bo'lsa — Stream widget yo'qolgandan keyin ham callback chaqiraveradi
super.dispose();
}
}9. Sinxron I/O va og'ir hisob — main isolate'ni band qilmaslik#
Flutter'da UI va Dart kodi bitta isolate'da (main isolate) ishlaydi. Agar shu yerda katta JSON parslash, rasm qayta ishlash yoki og'ir siklik hisob-kitob qilinsa — UI butunlay muzlaydi, chunki freym chizish uchun ham shu isolate kerak.
// YOMON — 50 000 ta elementli JSON'ni main isolate'da parslash UI'ni muzlatadi
final data = jsonDecode(hugeJsonString);
// YAXSHI — compute() alohida isolate'da ishlaydi, natijani Future orqali qaytaradi
final data = await compute(jsonDecode, hugeJsonString);Bu haqda isolate mexanizmining o'zi [[03-asinxron-dasturlash]] bobida chuqurroq yoritilgan.
Ko'p uchraydigan xato javoblar#
| Savol/vaziyat | Junior darajadagi javob | Senior darajadagi javob |
|---|---|---|
| "Ilova sekin, nima qilasiz?" | "Kodni qayta yozaman" | "Avval DevTools Timeline'da qaysi freym va qaysi thread (UI/Raster) sabab ekanini o'lchayman, keyin tuzataman" |
ListView sekin | "shrinkWrap: true qo'yaman" | "Avval .builder ishlatilyaptimi tekshiraman, keyin item widget'lar ichida qimmat operatsiya yo'qligini tekshiraman" |
| Xotira o'sib boryapti | "Ilovani qayta ishga tushirish kifoya" | "DevTools Memory tab'da heap grafigini kuzataman, dispose() chaqirilmagan controller/subscription qidiraman" |
| Animatsiya qotib qoladi | "Animatsiya davomiyligini kamaytiraman" | "Animatsiyalanuvchi qismni RepaintBoundary bilan izolyatsiya qilaman va uni chaqiruvchi build() metodini tekshiraman" |
Tez-tez so'raladigan intervyu savollari#
Savol: Flutter'da freym nima va nega 16ms muhim?
Javob: 60 Hz ekranda har 16.6 ms'da bitta yangi freym chizilishi kerak.
Flutter bu vaqt ichida build → layout → paint → composite zanjirini
tugatishi kerak. Agar bu vaqtdan oshib ketsa, keyingi VSync signaligacha eski
freym qoladi — foydalanuvchi buni "jank" sifatida ko'radi. 120 Hz ekranlarda
bu byudjet 8.3 ms'gacha qisqaradi.
Savol: const widget performance'ga qanday ta'sir qiladi?
Javob: const widget compile vaqtida yaratiladi va o'zgarmas hisoblanadi.
Rebuild paytida Flutter eski va yangi widget instance'ini identity bo'yicha
solishtiradi (identical()) — agar ular bir xil const obyekt bo'lsa,
Flutter bu subtree'ni umuman qayta ko'rib chiqmaydi, hatto == orqali chuqur
solishtirishga ham hojat qolmaydi.
Savol: StatelessWidget va StatefulWidget orasidan qaysi biri "tezroq"?
Javob: To'g'ridan-to'g'ri taqqoslash noto'g'ri — ikkalasi ham xuddi shu
Element/RenderObject mexanizmidan foydalanadi. Farq shundaki, StatefulWidget
o'zgaruvchi holatga ega bo'lgani uchun ko'proq keraksiz rebuild manbai bo'lishi
mumkin, agar holat noto'g'ri boshqarilsa. To'g'ri arxitekturada ikkalasi ham
bir xil samarali.
Savol: Nega setState() butun widget subtree'sini emas, faqat shu State
egasi bo'lgan widgetni qayta quradi?
Javob: setState() faqat o'sha State obyektiga tegishli Elementni
"dirty" deb belgilaydi. Flutter faqat dirty deb belgilangan Element'lardan
boshlab qayta quradi — lekin bu subtree pastga tushadi: agar dirty widget
katta subtree'ning ildizi bo'lsa, butun subtree qayta quriladi (agar pastdagi
widget'lar const bo'lmasa yoki alohida ChangeNotifier orqali izolyatsiya
qilinmagan bo'lsa).
Savol: ListView.builder va oddiy ListView orasidagi farqni tushuntiring.
Javob: Oddiy ListView(children: [...]) barcha bola widget'larni darhol,
ro'yxat qurilish paytida yaratadi. ListView.builder esa itemBuilder
funksiyasi orqali lazy ishlaydi — faqat ekranda ko'rinadigan va
cacheExtent bo'yicha yaqin atrofdagi elementlarni quradi, ekrandan chiqqan
elementlarni esa tashlab yuboradi. Katta yoki cheksiz ro'yxatlar uchun
.builder majburiy.
Savol: Image.network chaqirilganda xotira qanday sarflanadi va uni
qanday kamaytirish mumkin?
Javob: Rasm tarmoqdan siqilgan holatda (masalan, JPEG) yuklanadi, lekin
ekranga chiqarish uchun siqilmagan piksel bufferiga dekodlanadi — bu hajm
original o'lchamga bog'liq, ko'rsatiladigan o'lchamga emas. cacheWidth/
cacheHeight parametrlari dekodlash o'lchamini cheklab, xotira sarfini
sezilarli kamaytiradi. Qo'shimcha ravishda cached_network_image disk-level
keshlash orqali qayta yuklab olishning oldini oladi.
Savol: RepaintBoundary qachon kerak, qachon kerak emas?
Javob: Kerak — kichik, tez-tez o'zgaruvchi widget katta, statik subtree
ichida bo'lganda (masalan, animatsiyali indikator katta ro'yxat ustida).
Kerak emas — har bir widget atrofiga qo'yish, chunki har bir RepaintBoundary
o'z layerini yaratadi, bu esa GPU xotirasi va compositing xarajatini oshiradi.
To'g'ri yondashuv — DevTools'da "Highlight Repaints" bilan haqiqiy muammoli
joyni topib, faqat o'sha yerga qo'shish.
Savol: Flutter DevTools'da qaysi tab qaysi muammo uchun ishlatiladi?
Javob: Timeline/Performance — freym vaqtini va qaysi thread (UI/Raster) sekinlikka sabab bo'layotganini ko'rsatadi. Widget rebuild profiler — keraksiz rebuild manbalarini topadi. Memory — heap o'sishi va xotira sizmalarini kuzatadi. CPU Profiler — qaysi funksiya ko'p vaqt olayotganini flame chart orqali ko'rsatadi.
Savol: Xotira sizmasi (memory leak) Dart'da qanday yuzaga keladi, agar til GC'ga ega bo'lsa?
Javob: GC faqat "hech kim ishora qilmayotgan" obyektlarni tozalaydi. Agar
StreamSubscription yoki listener dispose qilinmasa, u hali ham faol tinglab
turadi — demak, u orqali bog'langan obyektlar (masalan, State) "ishlatilmoqda"
deb hisoblanadi va GC tomonidan tozalanmaydi. Bu — Dart'dagi "leak"ning asosiy
mexanizmi: unutilgan referenslar, C++ dagi kabi qo'lda ajratilgan xotira emas.
Savol: compute() funksiyasi nima qiladi va qachon ishlatiladi?
Javob: compute() berilgan funksiyani alohida isolate'da ishga tushiradi
va natijani Future orqali qaytaradi. Og'ir, CPU-intensiv operatsiyalar
(katta JSON parslash, rasm qayta ishlash, murakkab hisob-kitob) uchun
ishlatiladi — bu operatsiyalar main isolate'ni band qilmasligi kerak, aks
holda UI freym chizishga vaqt topa olmay muzlaydi.
Savol: Nega ba'zi jamoalar Provider/Riverpod'da Selector/
context.select ishlatishni talab qiladi, oddiy Consumer/watch emas?
Javob: Consumer/watch butun modeldagi istalgan o'zgarishda rebuild
qiladi, hatto UI shu maydonga bog'liq bo'lmasa ham. Selector/context.select
faqat belgilangan bitta maydon o'zgarganda rebuild qiladi — bu katta
modellarda (masalan, 10+ maydonli UserProfile) keraksiz rebuild sonini
sezilarli kamaytiradi.
Savol: Jank va freym tushishi (dropped frame) qanday aniqlanadi va farqi bormi?
Javob: Freym tushishi — bu jankning texnik ko'rinishi: freym 16.6 ms byudjetdan oshib ketganda, ekran eski freymni yana bir marta ko'rsatishga majbur bo'ladi (freym "tushadi"). Jank — bu foydalanuvchi tomonidan sezilaydigan natija: harakatning silliq emas, "sakrab" ko'rinishi. Ikkalasi sabab-oqibat munosabatida — freym tushishi ko'p bo'lsa, jank kuchli seziladi.
Nima uchun bu mavzu senior intervyularda ajratib so'raladi#
Junior dasturchi ko'pincha "kodni optimallashtiraman" deb umumiy javob beradi. Senior dasturchidan esa o'lchash → sabab topish → maqsadli tuzatish tsiklini aniq tushuntirish kutiladi — chunki productionda "tuyg'u bilan" optimallashtirish ko'pincha noto'g'ri joyga vaqt sarflashga olib keladi.
Rebuild profilini o'qish
Haqiqiy (yoki demo) Flutter ilovasida DevTools orqali Widget rebuild
profiler'ni oching. Kamida bitta ekranda kutilmagan tez-tez rebuild bo'layotgan
widgetni toping va uni qanday izolyatsiya qilish (kichik widgetga ajratish,
const qilish yoki Selector bilan cheklash) mumkinligini yozing.
Maslahatni ko'rsatish
DevTools'ni oching, Widget rebuild profiler yoqilgan holda ilovangizda oddiy bir ekranni bir necha marta scroll qiling yoki tugmani bosing — qaysi widget kutilganidan ko'proq marta qayta qurilganini yozib oling.