İçeriğe geç
academia.sh

Ders 11 / 15

Derleme Sistemleri

Bağımlılığı yazılmamış bir başlığın kural tabanlı bir derleme modelinde neden hâlâ eski sürümde kaldığını, zaman damgası karşılaştırmasını C içinde kurarak ölçer.

Yayın Güncelleme

Bu derste

  • Bir derleme hedefinin bağımlılık kaydını ve zaman damgası kuralını C yapılarıyla kurar
  • Yazılmamış bir bağımlılığın hedefi neden eski sürümde bıraktığını ölçer
  • Bu görünürlük sorusunu bir önceki modülün aşama sorusundan ayırt eder
İçindekiler

Önceki konu bir kaynağın çıkışta kime bırakıldığını ölçtü — sahibi belli bir bloğun, elden çıkarken kimin elinde kaldığını. Bu ders aynı soruyu kaynak dosyadan derlenmiş programa taşıyor: bir başlık dosyası değiştiğinde, o değişikliğin bağlı olduğu her hedefe gerçekten ulaşıp ulaşmadığını. Ulaşmazsa hedef, yazan tarafın elindeki yeni sürümden habersiz, eski halinin bir kopyasını taşımaya devam eder.

Kural tabanlı bir derleme sistemi bu kararı tek bir karşılaştırmayla verir: bir hedefin bağımlı olduğu her girdinin zaman damgası, hedefin kendi üretildiği zaman damgasından daha mı yeni? Öyleyse yeniden üretilir. Bu kural doğru çalışması için hedefin bağımlılık listesinin eksiksiz olmasını ister — listede olmayan bir girdi, değişse bile hiç sorulmaz.

Bu karar tek bir hedefle sınırlı kalmaz. Bir program genellikle birden çok hedeften kurulur ve bu hedefler birbirine bağımlı olabilir: bir kaynak dosya bir başlığa, o başlık başka bir başlığa bağımlı olabilir. Kural tabanlı sistem bu zinciri geçişli olarak izler — zincirin en altındaki bir değişiklik, aradaki her hedefi tek tek yeniden üretim listesine sokar. Zincirin ortasında bir hedefin bağımlılık listesi eksikse, o hedeften sonraki her şey de bu eksikliği devralır; eksiklik yalnız bir hedefi değil, ona bağlı bütün alt ağacı etkiler.

Zaman Damgası ve Bağımlılık Kararı

Aşağıdaki model bir derleme hedefinin durumunu tutar: en son üretildiği zaman damgası ve o anda gömülen sürüm numarası. Karar tek bir işlevde toplanır.

// hedef.h — bir derleme hedefinin bagimlilik kaydini tutan yapi
#ifndef HEDEF_H
#define HEDEF_H

struct Hedef {
    int uretim_zamani;    /* en son uretildiginde okunan zaman damgasi */
    int uretilen_surum;   /* o uretimde govdeye gomulen surum numarasi */
};

/* Bagimliligin zaman damgasi hedefin uretim zamanindan yeniyse hedef
   yeniden uretilir ve uretilen_surum baslik_surumu'ne guncellenir. Bu
   cagri hic yapilmazsa -- yani bagimlilik listeye hic yazilmamissa --
   hedef eski durumunu degistirmeden korur. */
void bagimliligi_denetle(struct Hedef *h, int baslik_zamani, int baslik_surumu);

#endif

Kural tek yerde yaşar; başlık zamanı, hedefin kendi zamanından yeniyse yeniden üretim gerçekleşir.

// hedef.c — karar tek kuralla veriliyor: bagimlilik hedeften daha yeni mi
#include "hedef.h"

void bagimliligi_denetle(struct Hedef *h, int baslik_zamani, int baslik_surumu) {
    if (baslik_zamani > h->uretim_zamani) {
        h->uretilen_surum = baslik_surumu;
        h->uretim_zamani = baslik_zamani;
    }
}

Şimdi aynı başlangıç durumundaki iki hedef karşılaştırılıyor. yazilmis hedefi için bagimliligi_denetle başlığın güncel zaman damgası ve sürümüyle çağrılıyor — bağımlılık listede yazılı, derleme sistemi başlığın değiştiğini biliyor. yazilmamis hedefi için bu çağrı hiç yapılmıyor; sanki o hedefin bağımlılık listesinde başlık hiç geçmiyormuş gibi. Başlığın kendisi ikisi için de aynı şekilde 2. sürüme çıktı; fark yalnız derleme sisteminin bunu bilip bilmediğinde.

// olcum.c — yazilmis/yazilmamis karari ve uc halkali zincirin karari tek kosumda
#include <stdio.h>
#include "hedef.h"

int main(void) {
    struct Hedef yazilmis = { 10, 1 };     /* uretim_zamani=10, surum=1 */
    struct Hedef yazilmamis = { 10, 1 };   /* ayni baslangic durumu */

    /* baslik gercekte 2. surume, zaman damgasi 20'ye guncellendi */
    bagimliligi_denetle(&yazilmis, 20, 2);
    /* yazilmamis icin bu satir yok -- bagimlilik listede hic gecmiyor */

    printf("yazilmis bagimlilik uretilen_surum: %d\n", yazilmis.uretilen_surum);
    printf("yazilmamis bagimlilik uretilen_surum: %d\n", yazilmamis.uretilen_surum);

    /* Uc halkali zincir: temel -> ara -> son. Tam yazimda ara, temel'e
       bagimli oldugunu bildiriyor ve kendi kaydini guncelliyor; son da
       ara'ya bagli oldugu icin guncellenmis kaydi devraliyor. Eksik
       yazimda ara, temel'e bagimliligini hic bildirmiyor; son ara'ya
       dogru bagli olsa bile devralacak yeni bir kayit bulamiyor. */
    struct Hedef ara_tam = { 10, 1 };
    struct Hedef son_tam = { 10, 1 };
    struct Hedef ara_eksik = { 10, 1 };
    struct Hedef son_eksik = { 10, 1 };

    bagimliligi_denetle(&ara_tam, 20, 2);
    bagimliligi_denetle(&son_tam, ara_tam.uretim_zamani, ara_tam.uretilen_surum);

    /* ara_eksik icin bagimliligi_denetle hic cagrilmiyor */
    bagimliligi_denetle(&son_eksik, ara_eksik.uretim_zamani, ara_eksik.uretilen_surum);

    printf("tam zincir son uretilen_surum: %d\n", son_tam.uretilen_surum);
    printf("eksik zincir son uretilen_surum: %d\n", son_eksik.uretilen_surum);
    return 0;
}
yazilmis bagimlilik uretilen_surum: 2
yazilmamis bagimlilik uretilen_surum: 1
tam zincir son uretilen_surum: 2
eksik zincir son uretilen_surum: 1

PA1. Zaman damgaları burada tamsayı sayaçlarıyla temsil ediliyor; gerçek bir dosya sisteminin taşıdığı takvim değeri değil. Karşılaştırmanın sonucu değişmiyor, çünkü kural yalnız sıralamaya bakıyor — hangi tamsayının büyük olduğuna.

yazilmis hedefi başlığın yeni sürümünü gördü ve uretilen_surum alanı 2’ye güncellendi. yazilmamis hedefi ise 1’de kaldı: başlık gerçekte değişmiş olsa da, derleme sisteminin bağımlılık listesinde bu ilişki hiç yazılı değildi, dolayısıyla karşılaştırma hiç çalıştırılmadı. yazilmamis.uretilen_surum alanı, yazan tarafın bıraktığı yeni değerden tamamen habersiz, eski sürümün bir kopyasını taşımaya devam ediyor. Bu tek karşılaştırma iki farklı yazımın kararını yan yana koyuyor: aynı başlangıç durumundan, aynı gerçek değişiklikten yola çıkıp, yalnız bağımlılık listesine yazılıp yazılmamasına göre birbirinden ayrışan iki sonuç.

PA2. Üç halkalı zincir burada üç ayrı struct Hedef değişkeniyle temsil ediliyor; gerçek bir derleme sisteminde bu ilişki bir bağımlılık grafiğinde tutulur ve zincir uzunluğu sabit değildir. Modelin ölçtüğü karar — bir halkanın eksikliğinin zincirin geri kalanına yayılması — grafiğin boyutundan bağımsızdır.

ara_tam ile son_tam, temel’in güncellendiğini iki adımda devralarak son sürümü 2’ye taşıyor: ara_tam temel’e bağlı olduğunu bildiriyor, son_tam da ara_tam‘a bağlı olduğunu bildiriyor. ara_eksik ise temel’e bağlı olduğunu hiç bildirmediği için kendi kaydı 1’de donuk kalıyor; son_eksik’in ara_eksik’e olan bağlılığı tam yazılmış olsa bile, devralacağı yeni bir kayıt zaten yok — son_eksik.uretim_zamani ile ara_eksik.uretim_zamani eşit çıktığı için karşılaştırma baslik_zamani > h->uretim_zamani koşulunu hiç sağlamıyor. Bu, zincirin ortasındaki tek bir eksik kaydın neden yalnız kendi hedefini değil, ona bağlı bütün alt ağacı da etkilediğini somutlaştırıyor: son halka kendi bağlılığını doğru yazmış olsa bile, önceki halkanın eksikliğini telafi edemiyor.

Gerçek bir kaynak ağacında zincir üç halkayla sınırlı kalmaz ve dahası doğrusal da değildir: bir ara hedefe onlarca son hedef bağlı olabilir, ve bunların hepsi aynı eksikliği aynı anda devralır. ara_eksik örneğinde tek bir son_eksik vardı; gerçek bir ağaçta bu, ara_eksik’e bağlı her hedefin eski sürümde kalması demektir — eksikliğin etkisi tek bir hedefte değil, o hedefe bağlı bütün hedeflerde aynı anda görünür. Bu yayılma, zincirin ortasındaki bir eksikliği bulmayı hem daha önemli hem de bir bakıma daha kolay kılar: birden fazla hedefin aynı anda ve aynı şekilde eski kaldığını görmek, tek bir hedefin izole bir şekilde eski kalmasından daha belirgin bir örüntüdür ve genelde araştırmayı doğrudan ortak ataya, yani ara_eksik’e yönlendirir.

Bazı derleme sistemleri bu listeyi elle yazdırmak yerine bir üreteçten türetir: kaynak dosyaları tarayıp hangi başlığın hangi hedefe dahil edildiğini kendisi çıkarır ve bağımlılık listesini otomatik üretir (model). Üreteç tabanlı yaklaşımın kazandırdığı şey listenin unutulma olasılığını azaltmaktır — kural kendisi aynı kalır, yalnız listeyi kim yazdığı değişir.

Bu iki yaklaşım arasındaki fark, elle yazılan listenin statik, üretilen listenin ise her derleme öncesi yeniden çıkarılan olmasıdır. Elle yazılan liste bir kaynak dosya yeni bir başlık eklediğinde güncellenmezse eksik kalır; üreteç ise her koşumda kaynağın kendisine bakarak listeyi yeniden kurduğu için bu unutmayı yapısal olarak dışlar. Bedeli, üreteç adımının kendisinin de doğru çalışması gerekliliğidir — üreteç bir başlığı atlıyorsa, sonuç yine elle yazılmış eksik bir listeyle aynı sınıfa düşer, yalnız eksikliğin kaynağı insan değil üretecin kendisi olur.

Genel hatlarıyla üreteç tabanlı yaklaşım şöyle çalışır: derleyici (ya da ona eşlik eden ayrı bir araç) bir kaynak dosyayı işlerken hangi başlıkların gerçekten dahil edildiğini zaten görür — çünkü önişlemci aşaması her #include satırını açar. Bu bilgi atılmak yerine ayrı bir kayıt dosyasına yazılır: kaynak dosyanın adı ve o kaynağın geçişli olarak bağlı olduğu her başlığın listesi. Bu kayıt dosyası bir sonraki derlemeden önce okunur ve derleme sisteminin bağımlılık listesine dahil edilir. Sonuç olarak liste elle değil, kaynağın kendi gerçek içeriğinden türetilmiş olur; bir kaynak dosya yeni bir başlık eklediğinde, bir dahaki derlemede üreteç bu değişikliği kendiliğinden yakalar ve kayıt dosyasını günceller. Küçük bir kaynak ağacında elle yazılan liste sorun çıkarmayabilir — birkaç dosya, birkaç başlık, hepsi tek bakışta görülebilir. Ağaç büyüdükçe, her yeni #include satırının bir yerde de listeye elle eklenmesi gerekliliği ölçeklenmiyor: eklemeyi unutmak, kod yazmaktan daha kolay hâle geliyor, çünkü liste güncellemesi kaynak dosyayı değiştirmekle aynı anda, aynı dikkatle yapılmıyor.

Üreteç tabanlı yaklaşımın kendi maliyeti de var: kayıt dosyası bir önceki derlemenin çıktısıdır, dolayısıyla ilk derlemede — henüz hiçbir kayıt dosyası üretilmemişken — derleme sistemi bağımlılık listesi olmadan başlamak zorunda kalır. Bu, birçok kural tabanlı sistemin neden iki geçişli çalıştığını açıklar: önce var olan kayıt dosyalarını (varsa) okuyup bağımlılık grafiğini kurar, sonra gerçek derlemeyi bu grafiğe göre sıralar. Kayıt dosyası eksikse ya da bozuksa, sistem o hedefi güvenli tarafta kalıp yeniden üretir — eksik bilgiyi “değişmedi” değil “bilinmiyor, o yüzden üret” olarak okur. Bu, elle yazılan listenin sessizce eksik kalma riskine karşı üreteç tabanlı yaklaşımın yapısal avantajıdır: bir hata olduğunda sonuç genelde gereksiz bir yeniden derleme olur, sessizce atlanan bir derleme değil.

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

Bu model yalnız derleme sisteminin bildiği bağımlılık ilişkisini görüyor; başlığın içeriğini gerçekten okuyup okumadığını değil. bagimliligi_denetle çağrılmadığı sürece model, başlığın hiç değişmediğini varsaydığı bir durumla aynı sonucu üretir — oysa başlık gerçekten değişmiştir. Ölçülen şey bağımlılık kaydının doğruluğu; kaynağın kendisinin doğruluğu değil. Bu ayrım önemli çünkü iki farklı hata sınıfını karıştırmamak gerekiyor: kaydın kendisi bozuk olabilir (bu dersin konusu) ya da kayıt doğru olduğu hâlde derleyicinin o kaydı kullanma biçimi hatalı olabilir — örneğin bir önceki modülün aşama sorununa giren bir durum. İkisi de sonuçta “hedef beklenenden eski davranıyor” belirtisini verir, ama kökleri tamamen ayrıdır ve düzeltmesi de ayrıdır: birincisi bağımlılık listesine eksik satırı eklemekle, ikincisi ilgili aşamayı (önişlemci, derleyici, bağlayıcı) yeniden incelemekle çözülür.

Bir önceki modülde bir değişikliğin hangi aşamada göründüğü sorulmuştu: önişlemci mi, derleyici mi, bağlayıcı mı, yoksa çalışma zamanı mı. Buradaki soru farklı bir eksende durur — başlığı değişen ama yeniden derlenmeyen bir birim, aşama sorusu açısından sıradan bir önişlemci girdisidir; görünürlük sorusu açısından ise kopyadır, çünkü hedef değişikliği hiç görmemiştir.

Bu iki eksenin birbirinden bağımsız olması, bir hatayı ayıklarken hangi soruyu sorduğunuzu netleştirmeyi gerektirir. Bir hedefin beklenenden eski davranması iki farklı nedenden kaynaklanabilir: ya kaynak dosya doğru aşamadan geçmedi (önişlemci bir makroyu genişletmedi, derleyici eski bir nesne dosyasını kullandı), ya da kaynak dosya doğru aşamalardan geçti ama derleme sistemi onu hiç yeniden derlemedi. Bu modelin ölçtüğü yalnız ikincisi — hedefin uretilen_surum alanına hangi değerin ulaştığı, o değerin nasıl işlendiği değil. Bir derleme sistemi günlüğü bu iki durumu ayırt etmenin en doğrudan yoludur: hangi hedeflerin yeniden üretildiği, hangilerinin hiç dokunulmadan bırakıldığı günlükte görünür, ve beklenmedik şekilde dokunulmamış bir hedef bu dersin ölçtüğü sınıfın somut bir belirtisidir.

Zincirleme örnek bu iki ekseni birlikte gösteriyor. son_eksik’in eski sürümde kalması tek bir eksik kayıttan kaynaklanıyor — ara_eksik‘in temel’e bağımlılığını bildirmemesi — ama bu eksikliğin görünen yeri zincirin sonunda. Bir hatayı ayıklarken son_eksik’i inceleyen biri önce kendi bağımlılık listesine bakar, orayı doğru bulur (son_eksik, ara_eksik’e bağlı olduğunu doğru bildirmiş) ve hatayı orada aramaz; gerçek eksiklik bir önceki halkada durur. Bu, geçişli bir zincirde hatayı ayıklamanın neden tek bir hedefe bakmakla bitmediğini gösteriyor — sorun, zincirin görünen ucundan değil, zincirin kendisi boyunca geriye doğru izlenerek bulunur. Büyük bir kaynak ağacında bu iz sürme elle yapılamayacak kadar çok halka içerebilir; bu yüzden üreteç tabanlı yaklaşımın kayıt dosyaları aynı zamanda bir tanı aracı gibi de kullanılır — hangi hedefin hangi başlıklara gerçekten bağlı olduğunu, insan hafızasına değil dosyaya bakarak doğrulamak için.

Özet

  • Kural tabanlı bir derleme sistemi, hedefin bağımlı olduğu her girdinin zaman damgasını hedefin kendi üretim zamanıyla karşılaştırır.
  • Bağımlılık listesi eksiksiz olmadığında, listede yer almayan bir değişiklik hiç sorulmaz ve hedef eskisini korur.
  • yazilmamis hedefinin uretilen_surum alanı, başlığın gerçek sürümünden bağımsız, yazan tarafın eski bıraktığı değerin bir kopyasıdır — bu dersin ölçtüğü sınıf kopya.
  • Üreteç tabanlı yaklaşım bağımlılık listesini otomatik türeterek unutulma olasılığını azaltır; kuralın kendisini değiştirmez.
  • Görünürlük sorusu, bir önceki modülün aşama sorusundan bağımsız bir eksendir.
  • Bağımlılık zinciri geçişlidir: zincirin ortasındaki eksik bir kayıt, ona bağlı bütün alt ağacı da eski durumda bırakır — hata zincirin görünen ucunda değil, eksik kaydın bulunduğu halkada aranmalıdır; üreteç tabanlı sistemler eksik bilgiyi “değişmedi” değil “bilinmiyor” olarak okuyup güvenli tarafta kalıp yeniden ürettiği için hata payı gereksiz derlemeye kayar, sessiz atlamaya değil.

Pratikte bir bağımlılığın listeye yazılmadan kalmasının birkaç tipik nedeni var. Bir kaynak dosya yeniden düzenlenip yeni bir başlığa bölündüğünde, eski listenin güncellenmesi ayrı bir adımdır ve atlanabilir. Bir dosya bir dizinden başka bir dizine taşındığında, listedeki yol eski konumu göstermeye devam edebilir; kural bu durumda ya hiç eşleşmez ya da yanlış dosyayı izler. Kopyala-yapıştırla oluşturulan yeni bir hedef, kaynağındaki bağımlılık listesini de kopyalar ama kendi gerçek bağımlılıklarını yansıtmayabilir — yeni hedefin kendine özgü başlıkları listeye hiç girmemiş olur. Bu üç durum da aynı sonuca varıyor: liste, kaynağın şu anki gerçek durumunu değil, bir önceki düzenlemenin anlık görüntüsünü taşıyor. Üreteç tabanlı yaklaşımın kapattığı boşluk tam olarak bu — listenin kaynaktan ayrı bir yapı olarak elle bakımının hiç gerekmemesi.

Sonraki Adım

Bir hedef doğru derlense bile üretilen kod tek başına bir programa dönüşmez — çoğu zaman başka birimlerle bağlanır. Bu bağlanma da kendi zaman damgası sorusunu taşır: bir kütüphane güncellendiğinde, ona bağlı program bu güncellemeyi ne zaman görür — o an mı, yoksa bir sonraki yeniden derlemede mi, hatta hiç mi? Bir sonraki ders, bir kütüphaneye bağlanmanın iki farklı biçiminin, o kütüphane güncellendiğinde programın hangi sürümü göreceğini nasıl belirlediğini ölçüyor; bu derste kurulan “listede yazılı mı, değil mi” sorusunun bağlanma aşamasındaki karşılığı budur.

İ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