Bir yığının amacı nedir? Neden ihtiyacımız var?


320

Bu yüzden şimdi C # .NET uygulamalarımda hata ayıklamayı öğrenmek için MSIL öğreniyorum.

Her zaman merak ettim: yığının amacı nedir?

Sadece sorumu bağlama koymak için:
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?

  • Daha hızlı olduğu için mi?
  • RAM tabanlı olduğu için mi?
  • Verimlilik için mi?

CIL kodlarını çok daha derinlemesine anlamama yardımcı olmak için bunu anlamaya çalışıyorum .


28
Yığın belleğin bir parçasıdır, tıpkı yığın belleğin başka bir parçası gibi.
CodesInChaos

@CodeInChaos, değer türlerinden referans türlerine mi bahsediyorsunuz? ya da bu IL Kodları açısından aynı mıdır? ... yığının yığından daha hızlı ve daha verimli olduğunu biliyorum (ama bu değer / ref türleri dünyasında .. burada aynı olup olmadığını bilmiyorum?)
Jan Carlo Viray

15
@CodeInChaos - Sanırım Jan'ın referans verdiği yığın, işlev çağrıları sırasında yığın karelerini kabul eden bellek bölgesinin aksine, IL'nin yazdığı yığın makinesidir. İki farklı yığın ve
JIT'den

4
MSIL bilgisi .NET uygulamalarında hata ayıklamanıza nasıl yardımcı olur?
Piotr Perak

1
Modern makinelerde, kodun önbelleğe alma davranışı bir performans oluşturucu ve kırıcıdır. Bellek her yerde. Yığın, genellikle, sadece burada. Yığın gerçek bir şey olduğunu varsayarsak, sadece bazı kodların çalışmasını ifade etmek için kullanılan bir kavram değildir. MSIL çalıştıran bir platformun uygulanmasında, yığın kavramının onu gerçekten bitleri iten donanıma dönüştürmesi gerekmez.
Monica

Yanıtlar:


441

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.


63
VAY. Aradığım şey tam olarak bu. Bir cevap almanın en iyi yolu, ana geliştiriciden bir tane almaktır. Zaman ayırdığınız için teşekkürler, bunun derleyici ve MSIL'in karmaşıklıklarını merak eden herkese yardımcı olacağından eminim. Teşekkürler Eric.
Jan Carlo Viray

18
Harika bir cevaptı. Java kullanıcısı olmama rağmen blogunuzu neden okuduğumu hatırlatıyor. ;-)
jprete

34
@JanCarloViray: Çok hoş geldiniz! Ben öyleyim unutmayın bir Principal Geliştirici değil, asıl geliştirici. Bu takımda bu iş unvanına sahip birkaç kişi var ve ben en üst düzey ben bile değilim.
Eric Lippert

17
@Eric: Kodlamayı sevmeyi bıraktığınızda / ne zaman durursanız, programcılara öğretmeyi düşünmelisiniz. Eğlencenin yanı sıra, iş baskısı olmadan bir öldürme yapıyor olabilirsiniz. Harika yetenek o bölgede var (ve harika sabır, ben ekleyebilirsiniz). Bunu eski bir üniversite hocası olarak söylüyorum.
Alan

19
Yaklaşık 4 paragraf kendi kendime "Bu Eric gibi geliyor" diyordu, 5 ya da 6 tarafından "Evet, kesinlikle Eric" mezun oldu :) Başka gerçekten & epik kapsamlı bir cevap.
İkili Worrier

86

MSIL hakkında konuşurken sanal makine için talimatlardan bahsettiğinizi unutmayın . .NET'te kullanılan VM, yığın tabanlı bir sanal makinedir. Kayıt tabanlı VM'nin aksine, Dalvik VM , Android işletim sistemlerinde kullanılan bir örnektir.

VM'deki yığın sanaldır, VM talimatlarını işlemcide çalışan gerçek koda çevirmek yorumlayıcıya veya tam zamanında derleyiciye bağlıdır. .NET durumunda neredeyse her zaman bir titreşim olan MSIL komut kümesi, başlangıçtan itibaren gönderilecek şekilde tasarlanmıştır. Örneğin Java bayt kodunun aksine, belirli veri türleri üzerindeki işlemler için farklı talimatlara sahiptir. Bu da yorumlanması için optimize edilmiştir. Bir MSIL yorumlayıcısı aslında var, .NET Micro Framework'te kullanılıyor. Çok sınırlı kaynaklara sahip işlemcilerde çalışan, makine kodunu saklamak için gereken RAM'i karşılayamaz.

Gerçek makine kodu modeli, hem yığına hem de yazmaçlara sahip karışıktır. JIT kod optimize edicisinin en büyük işlerinden biri, kayıtlarda yığında tutulan değişkenleri depolamanın yollarını bulmak, böylece yürütme hızını büyük ölçüde artırmaktır. Bir Dalvik titremesinin tam tersi bir sorunu vardır.

Makine yığını, aksi takdirde çok uzun bir süredir işlemci tasarımlarında bulunan çok temel bir depolama tesisidir. Çok iyi bir referans lokasyona sahiptir, modern CPU'larda, RAM'i sağlayabildiğinden ve daha hızlı tekrarlayan verileri çok daha hızlı çiğneyen çok önemli bir özelliğe sahiptir. Dil tasarımı, yerel değişkenleri ve yöntem gövdesiyle sınırlı kapsamı destekleyen bir yığın olması nedeniyle büyük ölçüde etkilenir. Yığınla ilgili önemli bir sorun, bu sitenin adlandırılmış olmasıdır.


2
Çok ayrıntılı bir açıklama için +1 ve diğer sistemlere ve dile ekstra DETAYLI karşılaştırma için +100 (eğer yapabilirsem) :)
Jan Carlo Viray

4
Dalvik neden bir Kayıt makinasıdır? Sicne öncelikle ARM İşlemcilerini hedef alıyor. Şimdi, x86'da aynı sayıda kayıt vardır, ancak bir CISC olmakla birlikte, bunların sadece 4'ü yerelleri depolamak için gerçekten kullanılabilir, çünkü geri kalanlar ortak talimatlarda dolaylı olarak kullanılmaktadır. ARM mimarileri ise yerli halkın depolanmasında kullanılabilecek çok daha fazla sicile sahip olduğundan sicile dayalı bir yürütme modelini kolaylaştırıyorlar.
Johannes Rudolph

1
@JohannesRudolph Bu neredeyse yirmi yıldır doğru değil. Çoğu C ++ derleyicisinin hala 90s x86 komut kümesini hedeflemesi, x86'nın kendisinin yetersiz olduğu anlamına gelmez. Haswell'in 168 genel amaçlı tamsayı kaydı ve 168 GP AVX kaydı vardır - bildiğim herhangi bir ARM CPU'dan çok daha fazlası. (Modern) x86 derlemesinden tüm bunları istediğiniz gibi kullanabilirsiniz. Suç derleyici yazarlar, mimari / CPU değil. Aslında, ara derlemenin bu kadar çekici olmasının nedenlerinden biri - belirli bir CPU için bir ikili, en iyi kod; 90'lı yılların mimarisi ile hiçbir mucking.
Luaan

2
@JohannesRudolph .NET JIT derleyicisi aslında kayıtları oldukça yoğun kullanır; yığın çoğunlukla IL sanal makinesinin bir soyutlamasıdır, aslında CPU'nuzda çalışan kod çok farklıdır. Yöntem çağrıları doğrudan kayıt olabilir, yerel halk kayıtlara kaldırılabilir ... Makine kodundaki yığının ana yararı, altyordam çağrılarına verdiği yalıtımdır - bir kayda yerel koyarsanız, bir işlev çağrısı yapabilir bu değeri kaybedersiniz ve gerçekten söyleyemezsiniz.
Luaan

1
@RahulAgarwal Oluşturulan makine kodu, yığını herhangi bir yerel veya ara değer için kullanabilir veya kullanmayabilir. IL'de her argüman ve yerel yığın üzerindedir - ancak makine kodunda bu doğru değildir (izin verilir, ancak gerekli değildir). Bazı şeyler yığın üzerinde yararlıdır ve yığın üzerine yerleştirilir. Bazı şeyler öbek üzerinde yararlıdır ve öbek üzerine konurlar. Bazı şeyler hiç gerekli değildir veya bir kayıtta sadece birkaç dakikaya ihtiyaç vardır. Aramalar tamamen ortadan kaldırılabilir (satır içi) veya argümanları kayıtlara geçirilebilir. JIT'in çok özgürlüğü var.
Luaan

20

Bu konuda çok ilginç / ayrıntılı bir Wikipedia makalesi var , yığın makine talimat setleri avantajları . Tamamen alıntı yapmam gerekir, bu yüzden sadece bir bağlantı koymak daha kolaydır. Sadece alt başlıkları alıntılayacağım

  • Çok kompakt nesne kodu
  • Basit derleyiciler / basit çevirmenler
  • Minimum işlemci durumu

-1 @xanatos Çektiğiniz başlıkları özetleyebilir ve özetleyebilir misiniz?
Tim Lloyd

@chibacity Onları özetlemek isteseydim, bir cevap verirdim. Çok iyi bir bağlantı kurmaya çalışıyordum.
xanatos

@xanatos Hedeflerinizi anlıyorum, ancak böyle büyük bir wikipedia makalesine bağlantı paylaşmak harika bir cevap değil. Sadece googling yaparak bulmak zor değil. Öte yandan Hans'ın güzel bir cevabı var.
Tim Lloyd

@chibacity OP muhtemelen ilk önce arama yapamadığında tembeldi. Buradaki cevaplayıcı iyi bir bağlantı verdi (onu açıklamadan). İki kötülük bir iyilik yapar :-) Ve Hans'ı onaylayacağım.
xanatos

BÜYÜK bir bağlantı için answerer ve @xanatos +1. Birisinin tam olarak özetlemesini ve bilgi paketi cevabını bekliyordum .. Hans bir cevap vermeseydi, sizinkini kabul edilen cevap olarak yapardım .. bu sadece bir linkti, bu yüzden değildi cevabına iyi çaba harcayan Hans için adil .. :)
Jan Carlo Viray

8

Yığın sorusuna biraz daha eklemek için. Yığın kavramı, aritmetik mantık birimindeki (ALU) makine kodunun yığında bulunan işlenenlerde çalıştığı CPU tasarımından türetilir. Örneğin, çarpma işlemi üstteki iki işleneni yığından alabilir, çoğaltabilir ve sonucu tekrar yığına yerleştirebilir. Makine dili genellikle yığıntan işlenen eklemek ve kaldırmak için iki temel işleve sahiptir; PUSH ve POP. Birçok CPU'nun dsp'lerinde (dijital sinyal işlemcisi) ve makine denetleyicilerinde (çamaşır makinesini kontrol etmek gibi) yığın yonganın kendisinde bulunur. Bu, ALU'ya daha hızlı erişim sağlar ve gerekli işlevselliği tek bir yongada birleştirir.


5

Yığın / yığın kavramı izlenmezse ve veriler rastgele bellek konumuna yüklenirse VEYA veriler rastgele bellek konumlarından depolanırsa ... çok yapılandırılmamış ve yönetilmez olacaktır.

Bu kavramlar, performansı, bellek kullanımını ... ve dolayısıyla veri yapılarını geliştirmek için verileri önceden tanımlanmış bir yapıda depolamak için kullanılır.



0

"Kesme" yi aradım ve kimse bunu bir avantaj olarak görmedi. Bir mikro denetleyiciyi veya başka bir işlemciyi kesen her aygıt için, genellikle bir yığına aktarılan kayıtlar vardır, bir kesme servis yordamı çağrılır ve bittiğinde kayıtlar yığının dışına çıkarılır ve geri yerleştirilir idi. Daha sonra talimat işaretçisi geri yüklenir ve normal etkinlik kaldığı yerden devam eder, sanki kesinti hiç gerçekleşmemiş gibi. Yığınla, aslında birkaç cihaz (teorik olarak) birbirinizi kesintiye uğratabilirsiniz ve hepsi sadece çalışır - yığın yüzünden.

Yığın tabanlı diller ailesi de var. Birleştirici diller . Hepsi (inanıyorum) fonksiyonel dillerdir, çünkü yığın örtülü bir parametre geçirilir ve ayrıca değiştirilen yığın her işlevden örtük bir dönüştür. Hem Dördüncü hem de Faktör (mükemmel) diğerleriyle birlikte örnektir. Komut dosyası oyunları için Lua'ya benzer faktör kullanılmıştır ve şu anda Apple'da çalışan bir dahi olan Slava Pestov tarafından yazılmıştır. Onun youtube üzerinde Google TechTalk birkaç kez izledim. Boa yapıcıları hakkında konuşuyor, ama ne demek istediğinden emin değilim ;-).

Gerçekten, JVM, Microsoft'un CIL ve hatta Lua için yazılmış gördüğüm bazı VM'lerin, daha da fazla platforma taşınabilir hale getirmek için bu yığın tabanlı dillerin bazılarında yazılması gerektiğini düşünüyorum. Bu birleştirici dillerin VM oluşturma kitleri ve taşınabilirlik platformları olarak çağrılarını bir şekilde kaçırdığını düşünüyorum. ANSI C'de yazılmış ve daha da evrensel taşınabilirlik için kullanılabilen "taşınabilir" bir Forth olan pForth bile var. Emscripten veya WebAssembly kullanarak derlemeyi deneyen var mı?

Yığın tabanlı dillerde, sıfır noktası adı verilen bir kod stili vardır, çünkü herhangi bir parametre geçirmeden (zaman zaman) çağrılacak işlevleri listeleyebilirsiniz. Eğer fonksiyonlar birbirine mükemmel bir şekilde uyuyorsa, sıfır noktası fonksiyonlarının bir listesinden başka bir şeye sahip olamazsınız ve bu sizin uygulamanız olacaktır (teorik olarak). Forth veya Factor'a girerseniz, neden bahsettiğimi göreceksiniz.

at Forth Kolay , JavaScript ile yazılmış güzel bir çevrimiçi eğitim, burada küçük bir örnek (Not "sq sq sq sq" sıfır noktası tarzını çağıran bir örneği olarak) 'dir:

: sq dup * ;  ok
2 sq . 4  ok
: ^4 sq sq ;  ok
2 ^4 . 16  ok
: ^8 sq sq sq sq ;  ok
2 ^8 . 65536  ok

Ayrıca, Easy Forth web sayfası kaynağına bakarsanız, en altta yaklaşık 8 JavaScript dosyasında yazılmış çok modüler olduğunu göreceksiniz.

Forth'u asimile etmek için ellerimi alabileceğim neredeyse her Forth kitabına çok para harcadım, ama şimdi daha iyi bakmaya başladım. Sonra gelenlere kafa vermek istiyorum, eğer gerçekten almak istiyorsanız (bunu çok geç buldum), FigForth üzerine kitabı alın ve uygulayın. Ticari Forth'ların hepsi çok karmaşıktır ve Forth ile ilgili en büyük şey, yukarıdan aşağıya tüm sistemi kavramanın mümkün olmasıdır. Her nasılsa, Forth tüm geliştirme ortamını yeni bir işlemciye uygular, ancak ihtiyaççünkü her şeyde C ile geçiyor gibi görünse de, sıfırdan bir Forth yazmak için bir geçit ayini olarak hala yararlıdır. Yani, bunu yapmayı seçerseniz, FigForth kitabını deneyin - çeşitli işlemcilere aynı anda uygulanan birkaç Forth'dur. Bir çeşit Rosetta Forths Taşı.

Neden bir yığına ihtiyacımız var - verimlilik, optimizasyon, sıfır noktası, kesme sırasında kayıtları kaydetme ve özyinelemeli algoritmalar için "doğru şekil".

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.