İçeriğe geç
academia.sh

Ders 07 / 10

Denetim Akışı

Aynı davranış sınıf dosyasında iki ayrı arama komutuna derlenir: yoğun etiketli seçim doğrudan indeksleyen bir çizelge, seyrek etiketli seçim etiketler arasında arayan bir komut kurar; etiketli atlama düz `break`'e karşı hem adım hem doğru satır kazandırır, bayraklı biçime karşı ise sıfır adım.

İçindekiler

Önceki derste dal komutu bir işlecin yan ürünü olarak ortaya çıkmıştı: kısa devre değerlendirme, ikinci işlenene hiç girmeyen bir atlamaydı. Bu ders dalı asıl konusu yapar. Kaynakta if, switch, for, while, break ve continue diye ayrı ayrı yazılan biçimler, sınıf dosyasında bu çeşitliliği bulmaz. Orada yalnız birkaç tür komut vardır: koşullu dal, koşulsuz atlama ve iki tane seçim komutu.

Programlama Temelleri kursu koşullu dallanmayı, döngüyü ve döngü denetimini kavram olarak kurmuştu — bunlar tekrarlanmaz. Orada bir akış biçiminin ne yaptığı anlatıldı. Burada ölçülen şey, aynı davranışın hangi komuta derlendiği ve iki farklı komutun ayrı arama maliyeti taşıdığıdır.

Akış Biçimi ile Akış Komutu

Sınıf dosyasında bir seçimi karşılayan iki ayrı komut vardır ve derleyici hangisini kullanacağına etiketlerin dağılımına bakarak karar verir.

  • Yoğun seçim komutu bir çizelge tutar: en küçük etiketten en büyüğüne kadar her değer için bir giriş. Verilen değer çizelgeye doğrudan indekslenir; arama yoktur, tek bir konum hesabı vardır. Karşılığında çizelge, aradaki boşluklar için de yer tutar.
  • Seyrek seçim komutu yalnız gerçek etiketleri sıralı olarak tutar ve verilen değeri bu sıralı liste içinde arar. Yer tutmaz, ama her seçimde bir arama ödenir.

Üçüncü bir yol da vardır: aynı davranışı if zinciriyle yazmak. O zaman ortada hiç seçim komutu bulunmaz; etiket sayısı kadar koşullu dal art arda dizilir ve son etiket için hepsi denenir. Bu ders üç yazımı da kurs boyunca sürdürülen deponun raf numarası üzerinde ölçer, sonra döngü biçimlerine geçer.

Ölçüm Çekirdeği

Çekirdek önceki derslerinkidir; bu derste iki ölçüm okur. Birincisi dal komutu sayısıdır. İkincisi, yöntemde bir seçim komutu varsa hangisi olduğudur: yoğun seçimde çizelgenin kapsadığı aralık, seyrek seçimde tutulan etiket sayısı basılır.

  • SD16 — Dal sayısı sınıf dosyasındaki koşullu ve koşulsuz atlama komutlarının sayısıdır; kaç kez çalıştırıldıkları değil, kaç tane yazıldıkları sayılır.
  • SD17 — Seçim komutu tek bir yöntemde bir kez bulunur; sütun boş kaldığında o yöntemde hiç seçim komutu yok demektir.
// Olcek.java — akış biçiminin sınıf dosyasında hangi komuta derlendiğini okur
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    record Yontem(String ad, int komut, int dal, String secim, List<String> eklenen) {}

    static List<Yontem> oku(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Files.writeString(d.resolve(sinif + ".java"), kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow().run(bos, bos, "-d", d.toString(),
                d.resolve(sinif + ".java").toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        List<Yontem> yontemler = new ArrayList<>();
        for (MethodModel m : ClassFile.of().parse(d.resolve(sinif + ".class")).methods()) {
            if (m.code().isEmpty()) continue;
            List<String> eklenen = new ArrayList<>();
            String secim = "-";
            int komut = 0, dal = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) komut++;
                if (e instanceof BranchInstruction) dal++;
                else if (e instanceof TableSwitchInstruction t)
                    secim = "yogun " + t.lowValue() + ".." + t.highValue();
                else if (e instanceof LookupSwitchInstruction l)
                    secim = "seyrek " + l.cases().size() + " etiket";
                else if (e instanceof InvokeInstruction iv)
                    eklenen.add(iv.name().stringValue());
                else if (e instanceof TypeCheckInstruction tc && tc.opcode() == Opcode.CHECKCAST)
                    eklenen.add("tip denetimi");
            }
            yontemler.add(new Yontem(m.methodName().stringValue(), komut, dal, secim, eklenen));
        }
        return yontemler;
    }
}

On Akış Biçimi

Ölçülen sınıfın ilk üç yöntemi aynı yazımı üç ayrı etiket dağılımıyla verir; dördüncüsü üçüncüsünün eşlemesini if zinciriyle yazar. Sonraki dördü aynı toplamı dizide ve listede, gelişmiş for ile ve elle açılmış biçimle hesaplar. Son ikisi iç içe iki döngüden çıkmanın iki yolunu gösterir.

// Akis.java — on akış biçimi, sınıf dosyasında hangi komut
public class Akis {
    static final String KAYNAK = """
        import java.util.Iterator;
        import java.util.List;
        class Depo {
            static int yogunSecim(int raf) {
                switch (raf) { case 1: return 10; case 2: return 20;
                               case 3: return 30; case 4: return 40; default: return 0; }
            }
            static int bosluklu(int raf) {
                switch (raf) { case 1: return 10; case 2: return 20;
                               case 3: return 30; case 10: return 40; default: return 0; }
            }
            static int seyrekSecim(int raf) {
                switch (raf) { case 1: return 10; case 100: return 20;
                               case 10000: return 30; case 1000000: return 40; default: return 0; }
            }
            static int ifZinciri(int raf) {
                if (raf == 1) return 10;
                if (raf == 100) return 20;
                if (raf == 10000) return 30;
                if (raf == 1000000) return 40;
                return 0;
            }
            static int diziToplami(int[] adetler) { int t = 0; for (int x : adetler) t += x; return t; }
            static int diziIndeksli(int[] adetler) {
                int t = 0;
                for (int i = 0; i < adetler.length; i++) t += adetler[i];
                return t;
            }
            static int listeToplami(List<Integer> adetler) { int t = 0; for (int x : adetler) t += x; return t; }
            static int elleYineleyici(List<Integer> adetler) {
                int t = 0;
                for (Iterator<Integer> y = adetler.iterator(); y.hasNext(); ) { int x = y.next(); t += x; }
                return t;
            }
            static int etiketli(int[][] depo, int aranan) {
                int satir = -1;
                dis:
                for (int i = 0; i < depo.length; i++)
                    for (int j = 0; j < depo[i].length; j++)
                        if (depo[i][j] == aranan) { satir = i; break dis; }
                return satir;
            }
            static int bayrakli(int[][] depo, int aranan) {
                int satir = -1;
                boolean bulundu = false;
                for (int i = 0; i < depo.length && bulundu == false; i++)
                    for (int j = 0; j < depo[i].length; j++)
                        if (depo[i][j] == aranan) { satir = i; bulundu = true; break; }
                return satir;
            }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-16s %6s %4s %-16s %s%n",
                "yontem", "komut", "dal", "secim komutu", "kaynakta yazilmayan adim");
        for (Olcek.Yontem y : Olcek.oku(KAYNAK, "Depo")) {
            if (y.ad().startsWith("<")) continue;
            System.out.printf("%-16s %6d %4d %-16s %s%n", y.ad(), y.komut(), y.dal(), y.secim(),
                    y.eklenen().isEmpty() ? "-" : String.join(", ", y.eklenen()));
        }
    }
}
yontem            komut  dal secim komutu     kaynakta yazilmayan adim
yogunSecim           12    0 yogun 1..4       -
bosluklu             12    0 yogun 1..10      -
seyrekSecim          12    0 seyrek 4 etiket  -
ifZinciri            22    4 -                -
diziToplami          24    2 -                -
diziIndeksli         18    2 -                -
listeToplami         20    2 -                iterator, hasNext, next, tip denetimi, intValue
elleYineleyici       20    2 -                iterator, hasNext, next, tip denetimi, intValue
etiketli             32    6 -                -
bayrakli             38    7 -                -

İlk üç satır dersin ana ölçümüdür. Üç yöntem de dört etiket ve bir varsayılan dal taşıyor, aynı yazımla yazılmış ve sınıf dosyasında aynı 12 komuta dönüşüyor. Ama seçim komutu aynı değil: etiketler 1’den 4’e yayıldığında yoğun komut, milyonlara yayıldığında seyrek komut üretiliyor. Kaynakta iki switch arasında yapısal hiçbir fark yoktur; fark yalnız sayıların değerindedir ve derleyici o değerlere bakarak ayrı bir komut seçmiştir. Komut sayısı bunu görmez — burada sayılacak şey komut sayısı değil, hangi komut olduğudur.

İkinci satır kararın nasıl verildiğini gösteriyor. bosluklu yalnız dört etiket taşıyor, ama en küçüğü 1, en büyüğü 10; derleyici yine yoğun komutu seçiyor ve çizelge 1’den 10’a kadar uzanıyor. Yani sınıf dosyası kullanılmayan altı giriş için de yer tutuyor. Derleyicinin verdiği karar bir takastır: dolduran boşluklar dosyada yer kaplar, karşılığında her seçimde arama yerine tek bir indeksleme yapılır. Etiketler yeterince dağıldığında takas tersine döner.

Dördüncü satır seçimin alternatifidir. Aynı eşleme if zinciriyle yazıldığında ortada hiç seçim komutu yoktur: 22 komut ve 4 dal. Dört dalın anlamı, son etiketin bulunabilmesi için önceki üçünün de denenmesi gerektiğidir. Seçim komutları bu denemeyi tek adıma indirir; if zinciri etiket sayısıyla birlikte büyür. Aynı davranış, iki ayrı maliyet.

Aynı Döngü, İki Ayrı Adım

Ortadaki dört satır gelişmiş for döngüsünün tek bir yazım olmadığını gösteriyor.

diziToplami 24 komut ve kaynakta yazılmayan hiçbir adım üretiyor: derleyici gelişmiş for yazımını sayaçlı bir döngüye açıyor. listeToplami aynı gövdeyi 20 komutta bitiriyor ama beş eklenen adım taşıyor — yineleyici alınıyor, iki kez sorgulanıyor, dönen nesne denetleniyor ve sayıya açılıyor. Bu sayılar ilk derste kutulamanın bedeli olarak okunmuştu; burada okunuşu başkadır. Aynı yazım, gezilen şeyin tipine göre iki ayrı döngüye derleniyor. Sözdizim tek, komut iki.

Yanlarındaki iki satır bunu kanıtlıyor. diziIndeksli elle yazılmış sayaçlı döngüdür ve 18 komut üretiyor — gelişmiş biçimden altı komut az, çünkü gelişmiş biçim dizi referansını ve uzunluğu ayrı yuvalara kopyalar. elleYineleyici ise elle yazılmış yineleyici döngüsüdür ve listeToplami ile birebir aynı çıkıyor: aynı 20 komut, aynı iki dal, aynı beş adım. Liste üzerindeki gelişmiş for, tam olarak bu döngünün kısaltmasıdır; dizi üzerindeki ise sayaçlı döngünün biraz daha bol yazılmış halidir.

Son iki satır bir sonraki bölümün konusudur: etiketli atlama 32 komut ve 6 dal, bayraklı biçim 38 komut ve 7 dal.

Etiketli Atlamanın Ölçülen Kazancı

İç içe iki döngüden birden çıkmak için Java üç yol sunar: dış döngüye bir etiket koyup break dis yazmak, bir bayrak değişkeni tutmak, ya da yalnız break yazıp iç döngüden çıkmak. Üçü aynı programı vermez.

  • SD18 — Depo üç satır ve satır başına dört kayıt taşır; aranan ad iki satırda bulunur, ilki ikinci satırın üçüncü kaydıdır. Doğru yanıt 1’dir.
  • SD19 — Adım sayacı yalnız iç döngünün gövdesinde artırılır, yani karşılaştırılan kayıt sayısını sayar; süre ölçülmez.
// Atlama.java — üç çıkış biçimi, kaç adım ve hangi satır
public class Atlama {
    record Kayit(String ad, int adet, long agirlik) {}

    static int adim = 0;

    static Kayit[][] depo() {
        return new Kayit[][] {
            { new Kayit("vida", 4, 40L), new Kayit("pul", 9, 5L),
              new Kayit("somun", 2, 12L), new Kayit("mil", 1, 300L) },
            { new Kayit("conta", 7, 8L), new Kayit("yay", 3, 15L),
              new Kayit("civata", 5, 90L), new Kayit("bilye", 6, 20L) },
            { new Kayit("civata", 2, 90L), new Kayit("kama", 8, 30L),
              new Kayit("burc", 4, 60L), new Kayit("rulman", 1, 250L) },
        };
    }

    static int etiketli(Kayit[][] d, String aranan) {
        int satir = -1;
        dis:
        for (int i = 0; i < d.length; i++)
            for (int j = 0; j < d[i].length; j++) {
                adim++;
                if (d[i][j].ad().equals(aranan)) { satir = i; break dis; }
            }
        return satir;
    }

    static int bayrakli(Kayit[][] d, String aranan) {
        int satir = -1;
        boolean bulundu = false;
        for (int i = 0; i < d.length && bulundu == false; i++)
            for (int j = 0; j < d[i].length; j++) {
                adim++;
                if (d[i][j].ad().equals(aranan)) { satir = i; bulundu = true; break; }
            }
        return satir;
    }

    static int duzBreak(Kayit[][] d, String aranan) {
        int satir = -1;
        for (int i = 0; i < d.length; i++)
            for (int j = 0; j < d[i].length; j++) {
                adim++;
                if (d[i][j].ad().equals(aranan)) { satir = i; break; }
            }
        return satir;
    }

    public static void main(String[] args) {
        Kayit[][] d = depo();
        System.out.printf("%-12s %6s %8s%n", "bicim", "satir", "adim");
        adim = 0;
        System.out.printf("%-12s %6d %8d%n", "etiketli", etiketli(d, "civata"), adim);
        adim = 0;
        System.out.printf("%-12s %6d %8d%n", "bayrakli", bayrakli(d, "civata"), adim);
        adim = 0;
        System.out.printf("%-12s %6d %8d%n", "duz break", duzBreak(d, "civata"), adim);
    }
}
bicim         satir     adim
etiketli          1        7
bayrakli          1        7
duz break         2        8

Üçüncü satır ölçümün en önemli kısmıdır ve bir başarım farkı değildir. Düz break yalnız döngüden çıkar; dış döngü kaldığı yerden sürer, ikinci satırdaki eşleşme bulunduktan sonra üçüncü satır da taranır ve satir değişkeni yeniden yazılır. Sonuç 2, yani yanlış. Bir adım fazla ödenmiştir ve o adım yanlış yanıtı üretmiştir. İç içe döngülerde break’in hangi döngüye ait olduğu bir üslup sorusu değildir.

İlk iki satır ise etiketli atlamanın gerçek kazancını sınırlıyor. Etiketli biçim ile bayraklı biçim aynı satırı buluyor ve aynı 7 adımı ödüyor. Etiketli atlamanın bayraklı biçime karşı çalışma zamanı kazancı sıfırdır.

Sınırlayıcı Ölçüm: Kazanç Nerede, Bedel Nerede

Etiketli atlamanın ölçülebilir kazancı iki yerdedir ve ikisi de küçüktür.

  • SD20 — Karşılaştırma sınıf dosyası ölçümündeki etiketli ve bayrakli yöntemleridir; ikisi de aynı imzayı taşır ve aynı sonucu döndürür.

Birincisi sınıf dosyasındadır: etiketli biçim 32 komut ve 6 dal, bayraklı biçim 38 komut ve 7 dal üretiyor — altı komut ve bir dal az. Bayrak bir yuva tutar, her dış tur başında okunur ve eşleşmede yazılır; bunların hepsi komuttur. İkincisi kaynaktadır: bayraklı biçimde çıkış koşulu iki yere dağılır — dış döngünün başlığına ve iç döngünün gövdesine. Etiketli biçimde çıkış tek satırdadır.

Bedel de yazılmalıdır. Etiket, akışı okuyan kişinin gözünü kaynakta geriye gönderir: break dis satırını okuyan biri, dis etiketinin hangi döngüye konduğunu bulmak için yukarı bakmak zorundadır. İki döngüde bu bakış kısadır; üç ya da dört düzeyde, ve aralarında continue de varsa, etiketler akışı izlenmesi güç bir hale getirir. Ölçüm kararı vermez, yalnız neyin ne kadar olduğunu söyler: kazanç altı komut ve bir yuvadır, çalışma zamanı adımı olarak sıfırdır, ve bedel okuyanın yukarı bakmasıdır.

Bu sınır dersin tezini de tamamlıyor. Akış biçimleri arasındaki seçim, çoğu yerde komut sayısını değiştirir ama ödenen adımı değiştirmez. Gerçekten değişen tek yer, biçimin yanlış davranışı mümkün kıldığı yerdir — düz break satırındaki gibi.

Özet

  • Aynı seçim, etiketlerin dağılımına göre iki ayrı komuta derlenir: yoğun dağılımda doğrudan indeksleyen bir çizelge, seyrek dağılımda etiketler arasında arayan bir komut. Üçünde de komut sayısı 12’dir; değişen şey hangi komut olduğudur.
  • Yoğun komutun çizelgesi en küçük etiketten en büyüğüne uzanır: dört etiketli bir seçim 1’den 10’a kadar yer tutabilir. Karar bir takastır, dosya boyutuna karşılık arama.
  • Aynı eşleme if zinciriyle yazıldığında hiç seçim komutu bulunmaz: 22 komut ve 4 dal, ve son etiket için hepsi denenir.
  • Gelişmiş for tek bir yazım değildir: dizide 24 komut ve 0 eklenen adım, listede 20 komut ve 5 eklenen adım üretir; liste biçimi elle yazılmış yineleyici döngüsüyle birebir aynıdır.
  • Etiketli atlama düz break’e karşı hem bir adım hem de doğru satırı kazandırır; bayraklı biçime karşı kazancı altı komut, çalışma zamanı adımı olarak sıfırdır ve bedeli okuyanın etiketi yukarıda aramasıdır.

Sonraki Adım

Bu ders akış biçimlerinin hangi komuta derlendiğini ölçtü: aynı seçim iki ayrı arama komutuna, aynı gelişmiş for döngüsü dizide ve listede ayrı adıma dönüştü. Ölçümlerin hepsi bir şeyin üzerinde gezindi ve o şey hiç sorgulanmadı. Geriye üzerinde gezinilen şeyin kendisi kaldı. Sıradaki ders diziyi ölçer: new int[3] satırı sınıf dosyasında kaç komut bırakıyor, d.length yazımı bir alan okuması mı yoksa kendi başına bir komut mu, ve bir dizinin sınırını denetleyen adım kaynağın neresinde duruyor.

İ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