Stack a heap, teorie grafů
Lekce 07
Počítač a vykonávání programu
- "Mozkem" počítače je procesor
- Ten má na starosti (hlavně) vykonávaní instrukcí, velmi jednoduchých příkazů
- Např. přičti číslo někam, přečti byte z adresy v paměti, ulož něco do pamětí, atd.
- Pro zájemce je zde odkaz na instrukční sadu x86 (asi nejpoužívanější instrukční sada vůbec, běží na ni nejspíš i váš počítač)
- Bydlí na základní desce, která propojuje procesor se vším ostatním (grafickou kartou, vstupy a výstupy, operační pamětí, pevnými disky, atd.)
- Ten má na starosti (hlavně) vykonávaní instrukcí, velmi jednoduchých příkazů
- Při spuštění nativního spustitelného souboru (tedy souboru, který byl zkompilován pro příslušnou architekturu - třeba x86) se kód (ne náš kód, ale instrukce) přemístí do části operační paměti vyhrazené pro běh našeho programu
- Procesoru je řečeno, odkud má začít program vykonávat
- V paměti vyhrazené pro náš program bydlí také dvě její důležité části, zásobník (stack) a halda (heap)
- Zásobník zde zmiňovaný je podobný zásobníku jakožto datové struktuře, o haldě toto ale neplatí
Stack a heap
Hlavní rozdíly zásobníku a haldy jsou dva, přičemž jeden je důsledkem druhého. Způsob, jakým se k datům přistupuje, se liší. To zapříčiňuje odlišný čas přístupu k datům.
- Stack (zásobník volání)
- Slouží k uchovávání
- lokálních proměnných,
- parametrů volaných funkcí,
- tzv. return adres, které na stack dávají volající funkce, aby volaná funkce věděla, kde pokračovat s vykonáváním dalších instrukcí.
- Je podobný datové struktuře
- Máme pro něj instrukce
pop
apush
, kterými odebíráme nebo dáváme data na zásobník - Ze zásobníku ale můžeme číst jak chceme, prostě jen posuneme adresu stack pointeru, který ukazuje na konec zásobníku
- Máme pro něj instrukce
- Přístup k datům na zásobníku je rychlejší
- Můžeme na ně dát jen data, u kterých známe jejich velikost během překladu programu
- Během běhu programu se data ze stacku automaticky alokují a hlavně dealokují
- Slouží k uchovávání
- Heap (halda)
- Slouží k uchovávání
- dynamicky alokovaných proměnných (seznamy proměnné délky, slovníky, třídy, atd.)
- Halda je velký blok paměti, který se spravuje za běhu programu
- V jazyce C pomocí funkcí z rodiny
*alloc
, alokace pomocí funkcemalloc
- Také pomocí klíčových slov jako
new
- V jazyce C pomocí funkcí z rodiny
- Na haldu si za běhu může zapisovat každý co chce
- Problém je, když se alokovaná data přestanou využívat
- Již nevyužívaný prostor na haldě se musí uvolnit
- V jazyce C musíme manuálně a pečlivě volat funkci
free
, abychom paměť dealokovali- Pokud paměť neuvolňujeme, může nám dojít, tomu říkáme memory leak
- V high-level jazycích jako Python, C#, Java, apod. se dealokace provádí automaticky pomocí tzv. garbage collectoru
- Je to část programu, která běží paralelně s naším, kouká se na naši haldu a jakmile spatří nepoužívaná data, nelítostně je uvolní
- Přidává overhead našemu programu, může běh zpomalovat
- Máme několik strategií, jak garbage collector může fungovat
- Jednou z nich je reference counting, kdy si počítáme, kolikrát je objekt na haldě v programu zmíněný
- Jakmile počítadlo dosáhne nuly, garbage collector zasáhne
- V jazyce C musíme manuálně a pečlivě volat funkci
- Slouží k uchovávání
int main()
{
char *pismena_heap; // proměnná `pismena_heap` typu `char*`
// (tedy pointer na (nejspíše) seznam znaků)
// alokovaná NA STACKU
bool tautologie = true; // proměnná typu `bool` alokovaná na
// STACKU
if (tautologie)
{
char pismena_stack[100]; // proměnná o velikosti
// 100*sizeof(char), tedy 100 bytů
// alokovaná na STACKU
pismena_heap = new char[100]; // 100 bytů alokovaných na HEAPU,
// pointer na data na haldě
// se uloží do `pismena_heap`
// na STACK
} // zde končí if, `pismena_stack` nikde později není využívaná,
// je tedy odstraněna ze STACKU (ne garb. coll., ale instrukcí,
// které napsal překladač)
} // zde končí celý program. kdyby tady nekončil, zapomněli bychom
// dealokovat `pismena_heap`, vznikl by memory leak.
Poté, co program skončí, je jedno, jestli jsme paměť zapomněli dealokovat, operační systém ji odstraní sám. To neznamená, že bychom to měli dělat.
# řetězce jsou mutable, jsou předávany referencí
a = [1,2,3,4]
b = a
b.append(5)
print(id(a))
print(id(b))
# tyto dvě vytisknutí vypíší stejnou ID
print(b)
print(a)
# vypíše to stejné, protože pole je předáváno referencí
# typ `int` je immutable, při každé změně se vytvoří nový
# objekt na haldě. ano, v Pythonu je VŠE na haldě.
c = 5
d = c
d += 1
print(c) # 5
print(d) # 6
Základy teorie grafů
Uvažme následující situace:
- Na koncertě je tisíc lidí. Někteří lidé se znají, někteří ne. Jak najít nějvětší skupinku lidí, ve které se všichni znají?
- Kartograf nakreslil mapu silniční sítě mezi sídly v Česku. Jak najít nejkratší cestu z Prahy do Ostravy?
- Směnový manažer sepsal všechny potřebné procesy pro to, aby se materiály, které dorazí do továrny, zpracovaly a aby se z nich staly hotové produkty. Některé procesy závisí na jiných, které musí být dokončeny před nimi.
Všechny tyto situace mají jedno společné - to, o čem pojednávají, se dá nazvat jako "objekty o kterých mluvíme" a "vztahy, které mezi nimi jsou".
Objekty, o kterých pojednáváme, nazýváme vrcholy, vztahy mezi nimi hrany.
Formálně je graf $G$ dvojice $(V, E)$. Množina $V$ je množina vrcholů, zatímco $E$ množina neuspořádaných dvojic $\set{u, v}$, kde $u,v \in V$. Pro komfort píšeme místo $\set{u,v}$ jen $uv$.
Pokud nahradíme neuspořádané dvojice v množině $E$ uspořádanými, z obyčejného grafu se stane graf orientovaný.
Pokud chceme přidat ohodnocení hran, můžeme závest i funkci $h: E \to M$, kde $M$ je množina, ze které ohodnocení bereme. Tato funkce tedy přiřazuje hranám nějaké ohodnocení.
Teď trocha terminologie:
- Vrcholy spolu sousedí, pokud mezi nimi vede hrana
- Stupeň vrcholu $v$ odpovídá počtu hran, které do něj vedou (nyní definujme jen pro neorientované grafy)
O stupních platí jedno pěkné tvrzení. Říká se mu princip sudosti a říká, že se součet stupňů všech vrcholů rovná dvojnásobku počtu hran.
Z jednoho do druhého vrcholu můžeme dojít několika způsoby. Pokud vybereme jakoukoli posloupnost hran, říkáme této posloupnosti sled. Pokud se ve sledu neopakují hrany, této posloupnosti říkáme tah. Pokud se vrcholy neopakují, řekneme, že jde o cestu.
To, že lze z každého vrcholu dojít do každého jiného po cestě neznamená, že spolu vrcholy sousedí.
Slovo cesta je v teorii grafů dvojznačné. Jednak nám může říkat po jaké trase se vydat, abychom došli z jednoho vrcholu do druhého. Tato trasa nám ale také tvoří samostatný graf. Tento graf je jistě podgrafem grafu původního.
Dalším takovým typem grafu je kružnice, která má vrcholy $v_1,v_2,...,v_n$ a platí, že mezi $v_i$ a $v_{i+1}$ vede hrana. Musí ale také vést hrana mezi $v_n$ a $v_1$.
Pokud nemá graf jako podgraf kružnici, získává tím poměrně zajímavé vlastnosti. Říkáme jim stromy a platí, že
- mezi každými dvěma vrcholy vede právě jedna cesta
- přidáním libovolné hrany vytvoříme kružnici
- $|V| = |E| + 1$
Více si o grafech můžete přečíst zde.