İçeriğe geç
academia.sh

Ders 17 / 19

Başarım Ayarı

Gereksiz yeniden kurma ve yeniden düzen, başarım sorununun en sık kaynağıdır. Ders, bir değişikliğin ağaçta ne kadar yükseğe çıktığını ölçülen kayıtla gösterir.

Yayın Güncelleme

Bu derste

  • Gereksiz yeniden kurmayı, değişmeyen alt ağacı sabit kurucuyla kurarak azaltmanın mantığını açıklar
  • Yeniden düzen sınırının hangi kısıtla kurulduğunu söyler
  • Bir bileşenin isteği değişince kaç düğümün genişliğinin değiştiğini ölçülen kayıtla sayar
  • Başarım sorununu bütçe aşımından, kırpılan boyutlanmadan ayırır
İçindekiler

Önceki derste inceleyici bir sorunu gösterdi. Çoğu zaman sorun yanlış bir genişlik değil, gereksiz bir iştir: bir bileşenin değişmeyen alt ağacı her değişiklikte yeniden kurulur ya da yeniden ölçülür. Bu ders iki soruya bakar. Bir bileşenin değişikliği ağaçta ne kadar yükseğe çıkıyor? Ağaçta ne kadar iş yapılıyor?

Başarımın kendisi bir sayıdır ve bu ders sayı yazmaz; süre ölçmez. Yazılan şey, değişikliğin kaç düğüme dokunduğudur. Süre ve kare bütçesi başka bir işin konusudur.

Yeniden Düzen Sınırı

Birinci soru, değişikliğin ağaçta ne kadar yükseğe çıktığıdır. Bir bileşenin isteği değişirse ve ebeveyni çocuğun boyutunu okuyorsa, ebeveynin de yeniden düzenlenmesi gerekir; onun ebeveyni de çocuğunun boyutunu okuyorsa değişiklik yukarı doğru sürer. Çerçevenin belgesine göre ebeveyn çocuğun boyutunu okumuyorsa ya da çocuğa verilen kısıt sıkıysa, değişiklik orada durur: çocuğun yalnızca bir boyutu olabilir ve ebeveyn bunu bilir. Bu noktaya yeniden düzen sınırı denir.

Bu dersin ölçülebilir iddiası şudur: sıkı kısıt veren bileşen, altındaki değişikliğin yukarı çıkmasını durdurur. Modelde bu iddia, bir bileşenin isteği değişince hangi düğümlerin genişliğinin değiştiğini sayarak denenir.

Ölçülen Katman

Kursun her dersi aynı düz Dart modelini kullanır. Model bir bileşen ağacına ekran genişliğini verir ve her düğümün genişliğini kimin belirlediğini dört adla kaydeder. Aşağıdaki dosya çıktı üretmez; ölçülen bloklar onu içe alır.

// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}

Bu derste model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Modelde bir değişiklik, aynı ağaç işlevinin başka bir istekle yeniden çağrılması ve iki kaydın karşılaştırılmasıdır. Model çerçevenin yeniden kurma işini ya da zamanını taşımaz.

Sayılan Ekran

İki ağaç aynı sayacı, yani isteği değişecek yaprağı taşır. Birincisinde sayaç, 200 birimlik sabit bir panelin içindedir; panel çocuğuna sıkı kısıt verir. İkincisinde panel yalnızca bir iç boşluktur ve sayacın isteği panelin genişliğine geçer.

// counter_model.dart — sayacin iki ortami
import 'sizing.dart';

Part buildBounded(int want) => pad('Screen', 16, column('Body', [
      leaf('Title', 120),
      fixed('Panel', 200, leaf('Counter', want)),
    ]));

Part buildLoose(int want) => pad('Screen', 16, column('Body', [
      leaf('Title', 120),
      pad('Panel', 8, leaf('Counter', want)),
    ]));

Değişikliği sayan yardımcı, iki kaydı yola göre eşler. Yeni olan ya da genişliği değişen her düğümü sayar.

// change_count.dart — iki olcum arasinda genisligi degisen dugumleri sayar
import 'sizing.dart';

int countChanged(List<Sizing> before, List<Sizing> after) {
  var changed = 0;
  for (final entry in after) {
    final match = before.where((old) => old.path == entry.path).toList();
    if (match.isEmpty || match.first.width != entry.width) {
      changed++;
    }
  }
  return changed;
}

Sıkı Kısıtlı Panel

İlk ölçüm sınırlı ortamın tüm kaydını basar. Ekran 360 birimdir ve sayaç 100 birim ister.

// main.dart
import 'sizing.dart';
import 'counter_model.dart';

void main() {
  final log = measure(buildBounded(100), 360);
  printSizes(log);
}
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Title genislik=120 kisit=0..328
secilen: /Screen/Body/Panel genislik=200 kisit=0..328
dayatilan: /Screen/Body/Panel/Counter genislik=200 kisit=200..200

Panel 200 birim ister ve 200 alır: seçilen. Sayacın istediği ise bir önemi taşımaz, çünkü panel ona 200 ile 200 arasında sıkı bir kısıt verir. Sayacın genişliğini sayacın isteği değil panel belirler: dayatılan.

İstek Değişince

Şimdi sayacın isteği 100’den 300’e çıkar. Sıkı kısıtlı ortamda iki kaydı karşılaştırırız.

// main.dart
import 'sizing.dart';
import 'counter_model.dart';
import 'change_count.dart';

void main() {
  final before = measure(buildBounded(100), 360);
  final after = measure(buildBounded(300), 360);
  printSizes(after.where((entry) => entry.path.endsWith('Counter')).toList());
  print('degisen: ${countChanged(before, after)}');
}
dayatilan: /Screen/Body/Panel/Counter genislik=200 kisit=200..200
degisen: 0

Sayaç iki ölçümde de 200 birimdir ve hiçbir düğümün genişliği değişmemiştir. Değişiklik panelde durdu. Gerçek çerçevede de aynı fikir geçerlidir: sıkı kısıt veren bileşen, altındaki değişikliği yukarı taşımaz. Hangi düğümlerin gerçekten yeniden düzenleneceği ayrı bir iştir ve bu derste ölçülmez.

Sıkı Kısıt Vermeyen Panel

Aynı değişikliği iç boşluktan oluşan panelde deneriz. Burada panel çocuğuna sıkı kısıt vermez.

// main.dart
import 'sizing.dart';
import 'counter_model.dart';
import 'change_count.dart';

void main() {
  final before = measure(buildLoose(100), 360);
  final after = measure(buildLoose(300), 360);
  printSizes(after.where((entry) => entry.path.contains('Panel')).toList());
  print('degisen: ${countChanged(before, after)}');
}
secilen: /Screen/Body/Panel genislik=316 kisit=0..328
secilen: /Screen/Body/Panel/Counter genislik=300 kisit=0..312
degisen: 2

Sayacın 300 birimlik isteği 0 ile 312 arasına sığar ve seçilen kalır. Panel çocuğunun genişliğini iki yandan 8’er birim iç boşlukla sarar; sayaç değişince panelin genişliği de 116’dan 316’ya çıkar. İki düğüm değişti: sayaç ve panel. Sütun gövdesi sıkı kısıt aldığı için gövdeye ve ekrana kadar çıkmadı. Değişiklik sayaçtan bir basamak yukarı, panele çıktı ve gövdedeki sıkı kısıtta durdu.

Değişikliğin Yolunu Okumak

İki ölçüm aynı değişikliği farklı yollara sokar. Sıkı kısıtlı panelde sayacın yolu panelde biter: ebeveyn çocuğun isteğini hiç sormaz, çünkü cevabı önceden dayatmıştır. Gevşek panelde ise ebeveyn çocuğun boyutuna bağlıdır; panelin genişliği sayacın genişliğinden türer ve değişiklik bir basamak yukarı çıkar. Gövde bu zinciri keser, çünkü gövdenin genişliğini ekran dayatır.

Bu okuma bir başarım sorununu ararken işe yarar. Bir bileşen sık değişiyorsa önce ona ne kadar yukarıdan bağlı olduğuna bakılır. Yukarıdaki her ebeveyn çocuğun boyutunu okuyorsa, küçük bir değişiklik büyük bir alt ağacı yeniden düzenletebilir. Araya sıkı kısıt veren bir bileşen koymak bu zinciri kısaltır; bunun bedeli, çocuğun genişliğini artık çocuğun değil o bileşenin belirlemesidir. Sayaç 300 birim istese de 200 birimde kalır. Yani başarım kazancı ile boyutlanma özgürlüğü arasında bir alışveriş vardır ve bu alışverişi görmenin yolu, hangi düğümün dayatılan olduğuna bakmaktır.

Gereksiz Yeniden Kurma ve Sabit Kurucu

Çerçeve bir bileşen değiştiğinde onun altındaki ağacı yeniden kurar. Alt ağaç aynı kalıyorsa bu iş boşunadır. Çerçevenin belgesi, değişmez sabit kurucuyla kurulan bir bileşen için yeniden kurma işinin çoğunun atlanabileceğini söyler. Fikir basittir: değişmeyen parça her seferinde yeni bir nesne olarak üretilmez, aynı sabit nesne kullanılır.

Aşağıdaki parça iki biçimi yan yana gösterir; ölçülen modelin dışında, çerçeve kodudur.

// Platform kodu: sabit kurucu ve her seferinde yeni nesne
Column(
  children: [
    const Text('Baslik'),
    Text('Sayac: $counter'),
  ],
)

İlk çocuk hiç değişmez ve sabit kurucuyla kurulur. İkincisi sayaç değiştikçe değişir, bu yüzden sabit olamaz. Sabit kurucu yeniden kurma işini azaltır; boyutu, yani genişliği değiştirmez. Genişliği kimin belirlediği sorusu bu parçada aynı kalır.

Başarım Sorunu ile Bütçe Aşımı

M31/K06’da başarım, bir bütçenin aşılıp aşılmadığıyla sorulur; burada soru genişliği kimin belirlediğidir ve bütçe yoktur. Kırpılan bir bileşen bütçeyi aşmış sayılmaz; yalnızca isteği izin verilen aralığa sığmamıştır. Bu derste hiçbir süre yazılmadı ve yazılmayacak; yazılan şey bir değişikliğin kaç düğüme dokunduğudur.

Yaygın Yanılgı

Sabit kurucu bir bileşenin genişliğini değiştirir ya da korur. İkisi de yanlıştır. Sabit kurucu yeniden kurma işini azaltır; genişliği kimin belirlediği sorusu aynı kalır. Sayaç sabit kurucuyla da kurulsa, buildBounded içinde 200 dayatılan, buildLoose içinde 300 seçilen olurdu.

Sınırlayıcı Ölçüm

Son ölçüm, sınır kuralını sıkı kısıt ile gevşek kısıt arasındaki farka bağlar. Aynı değişiklik iki ortamda koşar ve yalnız değişen düğüm sayısı basılır.

// main.dart
import 'sizing.dart';
import 'counter_model.dart';
import 'change_count.dart';

void main() {
  final bounded = countChanged(measure(buildBounded(100), 360), measure(buildBounded(300), 360));
  final loose = countChanged(measure(buildLoose(100), 360), measure(buildLoose(300), 360));
  print('sinirli: $bounded');
  print('sinirsiz: $loose');
}
sinirli: 0
sinirsiz: 2

Odak düğüm /Screen/Body/Panel/Counter. Sıkı kısıtlı ortamda dayatılandır, değişen düğüm sayısı sıfırdır; gevşek ortamda seçilendir ve iki düğüm değişir. Dersin boyutlanması bu yüzden dayatılandır: sayacın genişliğini, değişen isteği değil, panelin sıkı kısıtı belirledi.

KY1. Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Modelde bir değişiklik iki kaydın karşılaştırmasıdır ve süre taşımaz. Çerçevenin yeniden kurma işi, nesne ömrü ve yeniden düzen sırası modelde yoktur. Yeniden düzen sınırının tam koşulları çerçevenin belgesindedir; model yalnız sıkı kısıtın etkisini örnekler.

Özet

  • Bu dersin boyutlanması: dayatılan. Sayacın genişliğini, değişen isteği değil, panelin sıkı kısıtı belirledi.
  • Sabit kurucu yeniden kurma işini azaltır; boyutu belirleyen kuralı değiştirmez.
  • Sıkı kısıt veren bileşen, altındaki değişikliğin yukarı çıkmasını durdurur; modelde değişen düğüm sayısı 0 oldu.
  • Gevşek kısıtta aynı değişiklik iki düğümü değiştirdi.
  • Kırpılan boyutlanma bir bütçe aşımı değildir; bu dersin kayıtları süre taşımaz.

Sonraki Adım

Başarım ayarı uygulamanın kendi ağacını hedefler. Ama uygulama çoğu zaman yalnız kendi kodundan oluşmaz: başkalarının yazdığı paketler de ağaca girer. Sıradaki ders, Paket Yönetimi, bir paketin sürümü değişince ağaçta ne olduğunu ele alır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat