Garbage collector
                              -=-=-=-=-=-=-=-=-=-

          One  day a student  came to Moon  and  said: "I  understand  how
      to make a better  garbage  collector. We must keep a reference count
      of the pointers to each cons."

          Moon patiently told the student the following story:

     "One day a student came to Moon and said: 'I understand how to
     make a better garbage collector...

                                 -- vtip z MIT
  (David Moon spolu s Tomem Knightem z větší části vytvořili lispové stroje.
    Reference couting má potíže zejména s objekty ukazujícími samy na sebe)

          Gabage collecting v překladu znamená "sběr smetí". Pokud je snad
      někdo touto činností  frustrován, může používat  mnohem  elegantněji
      znějicí název "resource regeneration", což znamená úplně totéž.

          Princip  garbage  collectingu je jednoduchý.  Program data pouze
      alokuje a o jejich  uvolňování  se vůbec  nestará. V případě,  že je
      nedostatek  paměti su aktivuje  tajemný  proces a automaticky  určí,
      které  bloky je možné  uvolnit. Tento  tajemný  proces  pracuje plně
      automaticky a od programátora  nepotřebuje  žádnou  pomoc. Je jasné,
      že takový přístup ušetří programátorovi mnoho starostí.  Nestane se,
      že  by  v  programu  zůstavaly  zbytečné   neuvolněné  bloky  či  že
      by program  používal  uvolněná data. Zejména u paraelních  programů,
      které využívají  komplikované datové struktury a na kterých  pracuje
      více lidí  často není  jasné, kdo vlastně kdy a kde má co uvolňovat.
      Zastánci garbage  collectingu  uvádějí, že vývoj programů se urychlí
      v průměru o 30%, což rozhodně není malá hodnota.

          Jediným problémem této skvělé myšlenky je, jak má tajemný proces
      fungovat.  Odpověď je ale poměrně  snadná. Pokud se na paměť  budete
      dívat jako na graf, kde vrholy jsou bloky paměti a orientované hrany
      určují  ukazatele  mezi  bloky,  snadno  zjistíte, že bloky na které
      neexistuje cesta po hranách se program už nemůže  dostat (nemá odkud
      by na ně získal  ukazatel). Takové bloky mohou být tedy bez starostí
      uvolněny.

                                 Algoritmy GC

          Pro  samotné  hledání   takových  bloků  existuje  hned  několik
      algoritmů.  Každého  začátečníka asi napadne  použít tzv. "reference
      couting",  kde  si  každý  blok  pamatuje,   kolik  na  něj  ukazuje
      ukazatelů.  Pokud  toto  čítadlo  klesne  na  0,  blok  je  uvolněn.
      Tento  algoritmus  se na první  pohled  zdá  být  naprosto  ideální:
      uvolňuje bloky hned, jak je to možné, nepotřebuje  příliš paměti ani
      času procesoru.  Přesto je zkušenějšími  sběrači  smetí  zatracován.
      Situace  totiž není  vůbec tak růžová, jak se na první  pohled  zdá.
      Tento  algoritmus  vůbec  neuvolní  bloky,  které  jsou  pospojovány
      do kruhu.  Přesto,  že z venčí na ně nikdo  neukazuje,  jsou  jejich
      čítače nastaveny na 1. Dalším nezanedbatelným problémem je, že čítač
      zabírá poměrně hodně paměti. Garbage collector se nejčastěji používá
      na velmi malé bloky  paměti, kde každý byte navíc  škodí.  Posledním
      problémem  je, že  program  sám se  musí  starat o udržování  těchto
      čítačů. V jazyce C na  to  neexistuje  žádná  automatická  konstukce
      a tak mohou vzniknout  přesně stejné chyby jako u volání free. V C++
      je v tomto ohledu  situace  lepší, přesto ale toto udržování  čítačů
      může védst k poměrně velkému zpomalení.

          Z toho  vyplývá,  že problém  garbage  collectingu  není  až tak
      jednoduchý, jak se na první  pohled  zdá.  Většina  lidí má tendenci
      problém  podcenit a udělat si na koleni nějaký collector, který sice
      většinou  funguje, ale  rozhodně  není  optimální. Je lepší  takovou
      práci přenechat profesionálům.

          Dvě základní metody  garbage  collectingu jsou: "mark and sweep"
      a "copy  propagation".  Myšlenky obou těchto  metod jsou jednoduché:
      U metody mark and sweep se jednou za čas rekurzivně projdou  všechny
      bloky, na které má proces  ukazatele a označkují se (mark). Potom se
      neoznačkované bloky vymažou  (sweep).  Jedná se vlastně o procházení
      grafem-bludištěm  pomocí  backtracingu.  Kam se nedostanete,  tam se
      nedostane ani program.  Metoda copy propagation  pracuje na odlišném
      principu.  Ta postupně  rekurzivně  kopíruje  všechny  bloky do nové
      paměti a  obnovuje  ukazatele  na  nová  působiště.  Takto  se  opět
      překopírují pouze dostupné bloky.

          Jakkoliv  tyto  metody  vypadají  hloupě a neefektivně,  chovají
      se velmi  dobře.  Výhoda  mark and sweep  oproti  reference  couting
      spočívá  zejména v tom,  že  uvolňuje  i cykly a  nepotřebuje  místo
      na čítač (značky mohou být udržovány v dočasném  bitovém poli). Copy
      porpagation ještě navíc zabraňuje  fragmentaci  paměti. Při samotném
      kopírování je sice potřeba  dočasně dvojnásobek paměti, ale kupodivu
      statistiky  ukazují, že u mark and sweep  alokace je často promrhané
      místo způsobené  fragmentací  paměti mnohem  větší. I když v průměru
      mark and sweep vede a proto je používanější.

          Samozřejmě, že tyto  metody mají i své nevýhody.  Mezi  největší
      patří fakt, že pracují  "návalově". Tedy že jednou za čas se program
      zastaví a provádí  garbage  collection.  Uživateli pak  nezbývá, než
      čekat. Je také těžké  určit okamžik, kdy tuto návalovou akci udělat.
      Zbytečně časté  volání  zdržuje,  jinak se ale zbytečně mrhá pamětí.
      Metoda "zavolej když je plná paměť" také neobstojí,  protože v UNIXu
      (a ani většině jiných modernějších  OS)není možné určit, kdy vlastně
      plná  je. Proto je nutné  dělat  různé  statistiky.  Navíc  existují
      úpravy těchto algoritmů, které pracují inkrementálně. Tady při každé
      alokaci  udělají  kus práce a program  pak  běží  plynule.  Navíc je
      možné, aby garbage collector běžel paralelně s vlastním programem.

          Více o algoritmech  se můžete  dočíst  ve  velmi  pěkném  článku
      "Uniprocessor Garbage Collection Techniques" [Wilson].

                                Conservative GC

          Jedním  z  hlavních   důvodů,  proč  garbage   collectory  stále
      patří  zejména do světa interpretovaných  jazyků je nutnost  rozumět
      datům  programu.   Popsané  metody  garbage   collectingu  musí  být
      schopny v bloku  najít  ukazatele a ty zpracovat.  Navíc musí vědět,
      na které  bloky  má program  přístup  přímo. V C je  situace  mnohem
      komplikovanější.  Garbage  collector datům  nerozumí a navíc počítat
      s daty  na  zásobníku a v registrech. S tímto se  vypořádává  postup
      zvaný  konzervativní  garbage  collecting.  Myšlenka je  jednoduchá.
      Garbage   collector   se  nesnaží o přesné   nalezení   zapomenutých
      bloků a všechny  data  na  zásobníku, v registrech a v blocích  jsou
      potencionální  ukazatele.  Při  značkovací  části  se  bere v  úvahu
      každý  z nich  a  pokud  ukazuje  do  nejakého  existujícího  bloku,
      blok se označí. Pokud se v tuto chvíli chytáte za hlavu, nezoufejte.
      Situace není tak strašná, jak se zdá. Díky opravdu vysoké efektivitě
      dnešních  garbage  collectorů  zpomalení  není až tak  velké.  Navíc
      garbage  collector u bloků umožňuje  většinou  nastavit,  které jsou
      atomické (další ukazatele už neobsahují) a na které se ukazuje jenom
      na začátek (aby se snížila  pravděpodobnost, že nějaký  integer bude
      chybně interpretován jako ukazatel doprostřed velkého bloku).

          Samozřejmě, že je nutné  program pro  takový  garbage  collector
      navrhnout. Ale i samotné  nahrazení  malocu a zrušení  všech  volání
      free  nepřináší  zase tak  strašné  výsledky.  Autoři  [Costs]  toto
      zkusili u několika paměťově intensivních programů (gawk, perl apod.)
      a výsledky  oproti  GNU malloc  implementaci  jsou asi  následující:
      Čas  strávený ve funkcích  alokátoru je u GNU malloc 17% a u garbage
      collectoru  36%.  Celkové  zpomalení  programu asi o 20%.  Maximální
      velikost použité paměti se zvýšila asi 2.5 krát.

          Běžně  ale  při  použití  garbage  collectingu  nemusíte na free
      úplně  zapomenout a použivat jej v jednoduchých  případech a garbage
      collectoru  přenechat  pouze ty komplikovanější.  Potom jsou  poměry
      mnohem příznivější.

                                 Bezpečnost GC

          Jakási "špinavost"  konzervativních  Garbage  Collectorů přináší
      další  problém:  najde GC  operavdu  všechny  ukazatele?  Ve většině
      případů  program  opravdu  uchovává  nějaký  ukazatel na blok.  ANSI
      C má velmi  striktní  pravidla pro práci s ukazatelem  (jak  jsem už
      popisoval,  zakazuje  například i udržovat ukazatele mimo alokovanou
      paměť) a proto většina programů odpovídajících ANSI C je korekní.

          Je ale nutné se vyvarovat zejména nasledujícím situacím:

      - ukládat a nahrávat ukazatele se souboru (nebo posílat po síti)
      - použití  memcpy pro zkopírování  ukazatelů na pozici, jejíž adresa
        není dělitelná velikostí ukazatele.
      - přetypovávání ukazatelů na integery
      - udržování  ukazatelů před daný blok (to je poměrně  častý problém,
        ale takové ukazatele stejně neodpovídají ANSI C)

          Jiným  problémem  jsou  ale  překladače.  Dobře   optimalizující
      překladač může udělat  nějaký  trik a ukazatel  schovat.  Překladače
      jsou tedy 100% spolehlivé  jenom bez optimalizací.  Pouze ty nejlépe
      optimalizující překladače porušují tyto pravidla a to jenom ve velmi
      vzácných  případech.  Přesto,  pokud chcete mít jistotu, je dobré si
      přečíst  [Boehm96].  Obsahuje  podrobný  návod, jak  zabránit  těmto
      problémům pod GCC, včetně  preprocesoru pro kód, který  udělá  změny
      automaticky. (a zpomalí kód přibližně o 10%). V boudocnosti vzhledem
      k rostoucí  popularitě těchto collectorů bude možná přidán  přepínač
      do GCC, který to zařídí sám.

                     Boehmův garbage collector pro C a C++

          Asi  nejrozšířenějším  garbage  collectorem  pro C je gc od pana
      Hanse  J. Boehma. Je  distribuován  pod  licencí  kompatibilní s GPL
      a dostupný na http://reality.sgi.com/employees/boehm_mti/gc.html.

          Patří  mezi velmi  kvalitní  konzervatavní  garbage  collecotry.
      Implementuje  algoritmus  Mark and Sweep a na některých  platformách
      i inkrementální  garbage  collecting  (práce se  rovnoměrně  rozdělí
      mezi  jednotlivé  volání  malloc). Má velice  kvalitní  podporu  pro
      multithreading  (může  probíhat  několik  alokací  zároveň) a volání
      finalizačních  funkcí  (funkce,  které se  zavolají  pokud se objekt
      uvolňuje).  Samotný  garbage   collector  sice  nelze  implementovat
      přenositelně,   ale  má  podporu   pro  velké   množství   platforem
      (GNU/Linux, UNIXové  systémy, DJGPP, Mac, Win32 apod.) a threadových
      knihoven  (protože  standard  pthreads nemá některé funkce, které GC
      potřebuje, je nutné psát podporu pro každou implementaci zvlášť).

          Je možné jej přeložit v mnoha režimech. Pokud jej chcete  použít
      pro existující kód, je možné jej přeložit  tak, aby nahradil  funkce
      malloc,  realloc a free.  Pokud  má Váš  kód  chyby, je  také  možné
      nastavit  flag,  aby se  free  ignorovalo.  Tak je možné  použít  GC
      pouze pro kontrolu a hledání  zapomenutých bloků a ve výsledném kódu
      jej vůbec  nepoužít. To ale není  cílem  tohoto  programu a proto má
      i vlastní interface. To se skládá z následujících volání:

      void * GC_malloc(size_t nbytes)
          podobně jako malloc alokuje n bajtů

      void * GC_malloc_atomic(size_t nbytes)
          Toto  je  dobré  použít  pro  oblasti,  které  neobsahují  další
      ukazatele. Mark průchod blok potom ignoruje a ušetří si práci

      void * GC_malloc_uncollectable(size_t nbytes)
          Funguje   stejně  jako  GC_malloc,  ale  výsledný   blok  nemůže
      být  uvolněn.  Narozdíl  od  systémového  malloc  ale GC o paměti ví
      a zkontroluje ji na případně ukazatele.

      void * GC_realloc(void *old, size_t new_size)
          Změna velikosti bloku

      void GC_free(void *dead)
          Uvolnění bloku

      void * GC_malloc_ignore_off_page(size_t nbytes)
      void * GC_malloc_atomic_ignore_off_page(size_t nbytes)
          Tyto    funkce    mají    stejnou    funkci    jako    GC_malloc
      a GC_malloc_atomic.  Při  mark  průchodu se ale berou v úvahu  pouze
      ukazatele do prvních  512  bajtů  bloku  (takové  ukazatele  by měly
      být typu  volatile, aby je optimizer  neposunul). Toto je doporučená
      metoda pro alokaci  bloků nad 100KB, aby se zabránilo  tomu, že mark
      průchod najde nějaký integer, co interpretovaný jako pointer ukazuje
      do bloku.

      void GC_gcollect(void)
          Explicitně zavola garbage collector.

      void GC_enable_incremental(void)
          Zapne inkrementální mód

      void GC_register_finalizer(...)
          Nastavení finalizační funkce pro objekt

      GC_warn_proc GC_set_warn_proc(GC_warn_proc p)
          Nastavení funkce pro vypisování warningů.

          Garbage collector navíc obsahuje  interface pro C++ (new, delete
      apod.) a speciální  knihovnu  pro  práci s velkými  stringy  (cord),
      která nejenom pěkně demonstruje  výhody  garbage  collectingu ale je
      i poměrně praktická. Jako příklad použití knihovny je ještě přibalen
      ukázkový editor pro curses a Win32.


            výheň