GÜNCELLEME: Bu soruyu o kadar çok sevdim ki , 18 Kasım 2011 tarihinde blogumun konusu haline geldim . Bu mükemmel soru için teşekkürler!
Her zaman merak ettim: yığının amacı nedir?
Çalışma zamanında gerçek iş parçacığı yığını değil, MSIL dilinin değerlendirme yığını demek istiyorum .
Neden bellekten yığın veya "yükleme" ye aktarım var? Öte yandan, neden yığıntan belleğe veya "depolamaya" transfer var? Neden hepsi sadece hafızaya yerleştirilmiyor?
MSIL bir "sanal makine" dilidir. C # derleyicisi gibi derleyiciler CIL oluşturur ve daha sonra çalışma zamanında JIT (Tam Zamanında) derleyici adı verilen başka bir derleyici IL'yi çalıştırabilen gerçek makine koduna dönüştürür.
Öyleyse "MSIL neden var?" Neden sadece C # derleyicisi makine kodunu yazmıyor?
Çünkü bu şekilde yapmak daha ucuz . Diyelim ki bu şekilde yapmadık; her dilin kendi makine kodu üretecine sahip olması gerektiğini varsayalım. Yirmi farklı diliniz var: C #, JScript .NET , Visual Basic, IronPython , F # ... Ve on farklı işlemciniz olduğunu varsayalım. Kaç tane kod üreticisi yazmanız gerekiyor? 20 x 10 = 200 kod üreteci. Bu çok iş. Şimdi yeni bir işlemci eklemek istediğinizi varsayalım. Kod üreticisini yirmi kez yazmanız gerekiyor, her dil için bir tane.
Ayrıca zor ve tehlikeli bir iştir. Uzman olmadığınız yongalar için verimli kod üreteçleri yazmak zor bir iştir! Derleyici tasarımcıları, yeni yonga kümelerinin verimli kayıt tahsisi konusunda değil, dillerinin anlamsal analizi konusunda uzmandır.
Şimdi varsayalım ki bunu CIL yolunda yapıyoruz. Kaç tane CIL jeneratörü yazmanız gerekiyor? Her dil için bir tane. Kaç tane JIT derleyiciniz yazmalısınız? İşlemci başına bir. Toplam: 20 + 10 = 30 kod üreteci. Dahası, CIL basit bir dildir ve CIL basit bir dildir, çünkü CIL basit bir dildir, çünkü CIL basit bir dildir. C # ve VB'nin tüm karmaşıklıklarından kurtuluruz ve her şeyi bir titreme yazmak için kolay olan basit bir dile indiririz.
Ara bir dile sahip olmak, yeni bir dil derleyici üretme maliyetini önemli ölçüde azaltır . Ayrıca yeni bir çipi destekleme maliyetini önemli ölçüde düşürür. Yeni bir çipi desteklemek istiyorsun, o çipte bazı uzmanlar buluyor ve onlara bir CIL titremesi yazmalarını sağlıyor ve işiniz bitti; daha sonra tüm bu dilleri çipinizde desteklersiniz.
Tamam, neden MSIL'e sahip olduğumuzu belirledik; çünkü ara bir dile sahip olmak maliyetleri düşürür. O zaman dil neden bir "yığın makinesi"?
Çünkü yığın makineleri dil derleyici yazarlarının ilgilenmesi için kavramsal olarak çok basittir. Yığınlar, hesaplamaları tanımlamak için basit, kolay anlaşılır bir mekanizmadır. Yığın makineleri de JIT derleyici yazarları ile başa çıkmak için kavramsal olarak çok kolaydır. Bir yığın kullanmak basitleştirici bir soyutlamadır ve bu nedenle yine maliyetlerimizi düşürür .
"Neden bir yığın var?" Neden her şeyi doğrudan hafızanın dışında yapmıyorsunuz? Bunu bir düşünelim. Şunun için CIL kodu oluşturmak istediğinizi varsayalım:
int x = A() + B() + C() + 10;
"Ekle", "çağrı", "depo" vb. Kurallara sahip olduğumuzu varsayalım. Bu C # için CIL kodu oluşturmak için sadece şöyle bir şey söylüyoruz:
load the address of x // The stack now contains address of x
call A() // The stack contains address of x and result of A()
call B() // Address of x, result of A(), result of B()
add // Address of x, result of A() + B()
call C() // Address of x, result of A() + B(), result of C()
add // Address of x, result of A() + B() + C()
load 10 // Address of x, result of A() + B() + C(), 10
add // Address of x, result of A() + B() + C() + 10
store in address // The result is now stored in x, and the stack is empty.
Şimdi varsayalım. Biz Senin yöntemini, yapacağım her işlemkodu işlenenlerinden adreslerini ve onun sonucunu saklayan adresi alır :
Allocate temporary store T1 for result of A()
Call A() with the address of T1
Allocate temporary store T2 for result of B()
Call B() with the address of T2
Allocate temporary store T3 for the result of the first addition
Add contents of T1 to T2, then store the result into the address of T3
Allocate temporary store T4 for the result of C()
Call C() with the address of T4
Allocate temporary store T5 for result of the second addition
...
Bunun nasıl gittiğini görüyor musunuz? Kodumuz büyükleşiyor çünkü normalde konvansiyonla sadece geçici olarak yığacak olan tüm geçici depoları ayırmamız gerekiyor . Daha da kötüsü, opcodlarımızın hepsi muazzam hale geliyor, çünkü şimdi hepsi, sonuçlarını yazacakları adresi ve her bir işlenenin adresini bir argüman olarak ele almak zorundalar. İki şeyi yığının dışına çıkaracağını ve bir şeyi koyacağını bilen bir "ekleme" komutu tek bir bayt olabilir. İki işlenen adresi ve bir sonuç adresi alan bir ekleme talimatı muazzam olacaktır.
Yığın tabanlı opcode'ler kullanıyoruz çünkü yığınlar ortak sorunu çözüyor . Yani: Geçici bir depolama alanı ayırmak, çok yakında kullanmak ve bitirdiğimde çabucak kurtulmak istiyorum . Elimizde bir yığınımız olduğu varsayımını yaparak, opcodları çok küçük ve kodu çok kısa yapabiliriz.
GÜNCELLEME: Bazı ek düşünceler
Bu arada, (1) bir sanal makine belirterek, (2) VM dilini hedefleyen derleyiciler yazma ve (3) VM'nin çeşitli donanımlara uygulamaları yazma ile maliyetleri önemli ölçüde düşürme fikri hiç de yeni bir fikir değil . MSIL, LLVM, Java bayt kodu veya diğer modern altyapılardan kaynaklanmadı. Bu stratejinin ilk uygulandığım uygulaması 1966'daki pcode makinesidir .
Bu konsepti bizzat duyduğum ilk şey, Infocom uygulayıcılarının Zork'u bu kadar çok farklı makinede nasıl çalıştırmayı başardığını öğrendiğim zamandı . Z- machine adında bir sanal makine belirlediler ve daha sonra oyunlarını çalıştırmak istedikleri tüm donanımlar için Z-makine emülatörleri yaptılar. Bu , ilkel 8-bit sistemlere sanal bellek yönetimi uygulayabilmeleri için büyük bir fayda sağladı ; bir oyun belleğe sığmayacağından daha büyük olabilir, çünkü sadece gerektiğinde kodu diskten girebilir ve yeni kod yüklemeleri gerektiğinde atabilirler.