Jaki jest cel stosu? Po co nam to?

Więc właśnie uczę się debugować moje aplikacje C#. Net.

Zawsze zastanawiałem się: jaki jest cel stosu?

żeby umieścić moje pytanie w kontekście:
Dlaczego jest transfer z pamięci do stosu lub " ładowanie?" Z drugiej strony, dlaczego istnieje transfer ze stosu do pamięci lub "przechowywanie"? Dlaczego nie umieścić ich wszystkich w pamięci?

  • to dlatego, że jest szybszy?
  • czy to dlatego, że jest Oparte na PAMIĘCI RAM?
  • dla efektywności?

Staram się to zrozumieć, aby pomóc mi zrozumieć CIL kody znacznie głębiej.

Author: svick, 2011-10-24

6 answers

UPDATE: tak bardzo spodobało mi się to pytanie, że zrobiłem je tematem mojego bloga 18 listopada 2011. Dzięki za świetne pytanie!

Zawsze zastanawiałem się: jaki jest cel stosu?

Zakładam, że masz na myśli stos ewaluacyjny języka MSIL, a nie rzeczywisty stos na wątek w czasie wykonywania.

Dlaczego jest transfer z pamięci do stosu lub " loading?"Z drugiej strony, dlaczego jest transfer ze stosu do pamięć czy "przechowywanie"? Dlaczego po prostu nie umieścić ich wszystkich w pamięci?

MSIL jest językiem "maszyny wirtualnej". Kompilatory takie jak kompilator C# generują CIL , a następnie w czasie wykonywania inny kompilator o nazwie JIT (Just In Time) kompilator zamienia IL w rzeczywisty kod maszynowy, który może wykonać. Więc najpierw odpowiedzmy na pytanie " po co w ogóle MSIL?"Dlaczego kompilator C# nie wypisuje kodu maszynowego?

Ponieważ jest tańsze aby to zrobić tędy. Załóżmy, że nie zrobiliśmy tego w ten sposób; Załóżmy, że każdy język musi mieć swój własny generator kodu maszynowego. Masz dwadzieścia różnych języków: C#, JScript. NET , Visual Basic, IronPython, F # ... I załóżmy, że masz dziesięć różnych procesorów. Ile generatorów kodu musisz napisać? 20 x 10 = 200 generatorów kodu. To dużo pracy. Teraz Załóżmy, że chcesz dodać nowy procesor. Musisz napisać generator kodu do niego dwadzieścia razy, po jednym dla każdego język.

Ponadto jest to ciężka i niebezpieczna praca. Pisanie wydajnych generatorów kodu dla żetonów, na których nie jesteś ekspertem, to ciężka praca! Projektanci kompilatorów są ekspertami w analizie semantycznej ich języka, a nie w efektywnej alokacji rejestrów nowych zestawów układów. Przypuśćmy, że zrobimy to w sposób CIL. Ile masz do napisania generatorów CIL? Po jednym na język. Ile kompilatorów JIT musisz napisać? Jeden na procesor. Razem: 20 + 10 = 30 generatorów kodu. Co więcej, generator language-to-CIL jest łatwy do napisania, ponieważ CIL jest prostym językiem, a generator CIL-to-machine-code jest również łatwy do napisania, ponieważ CIL jest prostym językiem. Pozbywamy się wszystkich zawiłości C# i VB i tak dalej i" obniżamy " wszystko do prostego języka, do którego łatwo napisać jitter.

Posiadanie języka pośredniego obniża koszty produkcji nowego kompilatora językadramatycznie . Obniża również koszty obsługi nowego chipa dramatycznie. Chcesz obsługiwać nowy chip, znajdź ekspertów na tym chipie i poproś ich o napisanie CIL jitter i gotowe; następnie obsługujesz wszystkie te języki na swoim chipie.

OK, więc ustaliliśmy, dlaczego mamy MSIL; ponieważ posiadanie języka pośredniego obniża koszty. Dlaczego więc język jest "maszyną stosu"?

Ponieważ maszyny stosowe są koncepcyjnie bardzo proste dla autorów kompilatorów języka. Stosy są prostym, łatwo zrozumiałym mechanizmem dla opisujące obliczenia. Maszyny stosowe są również koncepcyjnie bardzo łatwe do obsługi przez kompilatorów JIT. Korzystanie ze stosu jest uproszczoną abstrakcją, a zatem ponownie, obniża nasze koszty.

Pytasz "po co w ogóle mieć stos?"Dlaczego nie zrobić wszystkiego bezpośrednio z pamięci? Pomyślmy o tym. Załóżmy, że chcesz wygenerować kod CIL dla:
int x = A() + B() + C() + 10;

Załóżmy, że mamy konwencję, że "add"," call"," store " i tak dalej zawsze usuwają swoje argumenty stos i umieścić ich wynik (jeśli istnieje) na stosie. Aby wygenerować kod CIL dla tego C# wystarczy powiedzieć coś w stylu:

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.
Załóżmy, że zrobiliśmy to bez stosu. Zrobimy to po twojemu, gdzie każdy kod opcode pobiera adresy swoich operandów i adres, na który przechowuje swój wynik :
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
...
Widzisz jak to idzie? Nasz kod jest coraz ogromny, ponieważ musimy wyraźnie przydzielić wszystkie tymczasowe przechowywanie , które normalnie przez Konwent po prostu idź na stosie . Co gorsza, nasze same opcody stają się ogromne, ponieważ wszystkie muszą teraz wziąć jako argument adres, na który będą zapisywać swój wynik, oraz adres każdego operanda. Instrukcja "add", która wie, że usunie dwie rzeczy ze stosu i włączy jedną, może być pojedynczym bajtem. Instrukcja add, która zajmuje dwa adresy operandów i adres wynikowy, będzie ogromna.

Używamy opcodów opartych na stosie ponieważ stosy rozwiązują powszechny problem. Mianowicie: chcę przydzielić trochę tymczasowego magazynu, użyć go bardzo szybko, a potem szybko się go pozbyć, gdy skończę . Zakładając, że mamy do dyspozycji stos, możemy sprawić, że opcody będą bardzo małe, a kod bardzo zwięzły.

Aktualizacja: Kilka dodatkowych myśli

Nawiasem mówiąc, pomysł drastycznego obniżenia kosztów przez (1) specyfikację maszyny wirtualnej, (2) pisanie kompilatorów, które są skierowane na język VM, i (3) pisanie implementacji maszyny wirtualnej na różnych urządzeniach nie jest wcale nowym pomysłem. Nie powstała z MSIL, LLVM, Java bytecode, ani żadnej innej nowoczesnej infrastruktury. Najwcześniejszą implementacją tej strategii jest maszyna pcode z 1966 roku.

Pierwszy raz osobiście usłyszałem o tej koncepcji, kiedy dowiedziałem się, jak implementatorom Infocom udało się uruchomić Zork na tak wielu różnych maszynach. Określili wirtualny maszyna nazwała Z-machine , a następnie stworzyła emulatory Z-machine dla całego sprzętu, na którym chcieli uruchomić swoje gry. Miało to dodatkową ogromną korzyść, że mogli zaimplementować zarządzanie pamięcią wirtualną na prymitywnych systemach 8-bitowych; gra mogła być większa niż zmieściłaby się w pamięci, ponieważ mogli po prostu stronicować kod z dysku, gdy tego potrzebowali i odrzucić go, gdy potrzebowali załadować nowy kod.

 426
Author: Eric Lippert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-29 16:00:39

Pamiętaj, że kiedy mówisz o MSIL, to mówisz o instrukcji dla wirtualnej maszyny. Maszyna wirtualna używana w. NET jest maszyną wirtualną opartą na stosie. W przeciwieństwie do maszyn wirtualnych opartych na rejestrach, przykładem tego jest maszyna wirtualna Dalvik używana w systemach operacyjnych Android.

Stos w maszynie wirtualnej jest wirtualny, to do interpretera lub kompilatora just-In-time należy przetłumaczenie instrukcji maszyny wirtualnej na rzeczywisty kod, który działa na procesorze. Które w przypadku Z. NET jest prawie zawsze jitter, zestaw instrukcji MSIL został zaprojektowany, aby być jitted z get go. W przeciwieństwie do kodu bajtowego Javy, posiada on odrębne instrukcje dotyczące operacji na określonych typach danych. Co sprawia, że jest zoptymalizowany do interpretacji. Interpreter MSIL rzeczywiście istnieje, jest używany w. NET Micro Framework. Który działa na procesorach o bardzo ograniczonych zasobach, nie może sobie pozwolić na pamięć RAM wymaganą do przechowywania kodu maszynowego.

Rzeczywisty model kodu maszynowego to mieszane, posiadające zarówno stos, jak i rejestry. Jednym z dużych zadań JIT code optimizer jest wymyślenie sposobów przechowywania zmiennych, które są przechowywane na stosie w rejestrach, co znacznie poprawia szybkość wykonywania. Dalvik jitter ma przeciwny problem.

Stos maszyn jest w przeciwnym razie bardzo podstawowym magazynem, który istnieje w projektach procesorów przez bardzo długi czas. Ma bardzo dobrą lokalizację odniesienia, bardzo ważną cechą nowoczesnych procesorów, które przegryzają dane a dużo szybciej niż RAM może go dostarczyć i obsługuje rekurencję. Na projektowanie języka duży wpływ ma posiadanie stosu, widocznego w obsłudze zmiennych lokalnych, a zakres ograniczony do ciała metody. Znaczącym problemem ze stosem jest ten, którego nazwa pochodzi od tej strony.

 84
Author: Hans Passant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-08 15:27:43

Jest bardzo ciekawy / szczegółowy artykuł Wikipedii na ten temat, Zalety zestawów instrukcji maszynowych. Musiałbym to zacytować w całości, więc łatwiej jest po prostu umieścić link. Po prostu zacytuję podtytuły

  • bardzo zwarty kod obiektowy
  • Proste Kompilatory / proste Interpretatory
  • minimalny stan procesora
 20
Author: Peter Mortensen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-29 15:14:34

Aby dodać trochę więcej do pytania stosu. Koncepcja stosu wywodzi się z konstrukcji procesora, w której kod maszynowy w jednostce arytmetyczno-logicznej (ALU) działa na operandach znajdujących się na stosie. Na przykład operacja mnożenia może pobierać dwa górne operandy ze stosu, wiele ich i umieszczać wynik z powrotem na stosie. Język maszynowy ma zazwyczaj dwie podstawowe funkcje do dodawania i usuwania operandów ze stosu; PUSH i POP. W wielu procesorach DSP (digital signal processor) i sterowniki maszyn (takie jak sterujące pralką) stos znajduje się na samym chipie. Zapewnia to szybszy dostęp do ALU i konsoliduje wymaganą funkcjonalność w jednym chipie.

 8
Author: skyman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-26 00:30:32

Jeśli pojęcie stosu/sterty nie jest przestrzegane i dane są ładowane do losowej lokalizacji pamięci lub dane są przechowywane z losowych lokalizacji pamięci ... będzie bardzo niestrukturalny i niezarządzany.

Pojęcia te są używane do przechowywania danych w predefiniowanej strukturze w celu poprawy wydajności, wykorzystania pamięci ... i stąd nazywane struktury danych.

 5
Author: Azodious,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-24 12:14:05

Można mieć system pracujący bez stosów, stosując kontynuację stylu przekazywania kodowania. Wtedy ramki wywołania stają się kontynuacjami przydzielonymi w stercie śmieci zebranych (garbage collector potrzebowałby jakiegoś stosu).

Zobacz stare pisma Andrew appela: Kompilowanie z kontynuacjami i Garbage Collection może być szybsze niż alokacja stosu

(dzisiaj może się trochę pomylić z powodu problemów z pamięcią podręczną)

 4
Author: Basile Starynkevitch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-29 10:39:32