Přidělování paměti


Original: http://gee.cs.oswego.edu/dl/html/javaInCS.html

[Německá úprava a překlad tohoto článku se objeví v unix / pošta prosince 1996. Tento článek je nyní zastaralý a neodráží údaje o aktuální verzi malloc.]

Úvod

Paměťové rozdělovač tvoří zajímavé případové studie v inženýrství infrastrukturního softwaru. Jsem začal psát jeden v roce 1987 a byly zachovány a vyvinula ji (s pomoci mnoha dalších přispěvatelů), od té doby. Tento rozdělovač poskytuje implementací standardu C rutinami malloc (), free () a realloc (), stejně jako několik pomocných rutiny pomůcky. Rozdělovač nikdy nebyl dán určitý název. Většina lidí jen zavolat, že Doug Lea Malloc nebo dlmalloc v krátkosti.

Kód pro tuto rozdělovač byla umístěna ve veřejné sféře (k dispozici od ftp://g.oswego.edu/pub/misc/malloc.c), a je zřejmě široce používán: Slouží jako výchozí nativní verze malloc v některé verze systému Linux, je sestavené do několika běžně dostupných softwarových balíků (přepsání nativní funkce malloc), a byl použit v různých počítačových prostředích, stejně jako ve vestavěných systémech, a jistě mnoho dalších míst, které bych ani vědět.

Jsem napsal první verzi rozdělovač po napsání nějaké C + + programy, které se téměř výhradně opírá o přidělení dynamické paměti. Zjistil jsem, že oni se mnohem pomaleji a / nebo s mnohem větší celkovou spotřebu paměti, než jsem čekal, aby. Toto bylo kvůli charakteristikám paměťových allocators o systémech jsem běžel na (hlavně pak-aktuální verze SunOS a BSD). To zvrátí toto, nejdřív jsem napsal řadu účelových rozdělovačů v C + +, normálně přetížení operátoru nový pro různé třídy. Některé z nich jsou popsány v dokumentu o C + + přidělení techniky, které bylo upraveno do 1989 C + + Report článek Některé techniky přidělování paměti pro kontejnerové třídy.

Nicméně, brzy jsem si uvědomil, že vybudování speciální rozdělovač pro každou novou třídu, která inklinovala být alokována dynamicky a těžko použit nebyl dobrou strategii při budování druhy univerzálních tříd programování podpůrných jsem psal v té době. (Od roku 1986 do roku 1991 jsem byl hlavním autorem libg + +, GNU C + + knihovna). Širšího řešení bylo potřeba – napsat rozdělovač, který byl dost dobrý, za normálních C + + a C zátěže tak, že by programátoři nebude v pokušení napsat účelové alokátory kromě za velmi zvláštních podmínek.

Tento článek představuje popis některé z hlavních cílů návrhu, algoritmy a implementace úvahy pro tento rozdělovač. Podrobnější dokumentace lze nalézt kódu distribuce.

Cíle
Dobrý přidělování paměti musí zvážit řadu cílů:

Maximalizace kompatibility
Rozdělovač by měl být zásuvný modul kompatibilní s ostatními, zejména, že by se měl řídit ANSI / POSIX konvence.
Maximalizace Přenosnost
Spoléhání se na systém, neboť jen málo závislé funkce (například systémových volání) jak je to možné, a přitom poskytuje volitelnou podporu dalších užitečných funkcí k dispozici pouze na některých systémech, shodu všem známým omezením systému na sbližování a zabývají pravidly.
Minimalizace prostor
Rozdělovač nesmí ztrácet prostor: Je třeba získat co nejméně paměti ze systému jak je to možné, a měl by zachovat paměť způsobem, který minimalizuje fragmentaci – “” otvory v souvislých blocích paměti, které nejsou použity v programu.
Minimalizace času
Malloc (), free () a realloc postupy by měly být co nejrychleji v průměrném případě.
Maximalizace Tunability
Volitelné funkce a chování by měly být kontrolovatelné uživateli buď staticky (pomocí # define a podobně), nebo dynamicky (pomocí ovládacích příkazů, jako jsou mallopt).
Maximalizace Lokalita
Přidělení kusy paměti, které se obvykle používají společně blízko sebe. To pomáhá minimalizovat stránky a výpadků během vykonávání programu.
Maximalizace detekce chyb
To, že není možné pro univerzální rozdělovač také sloužit jako univerzální testování paměti chyb nástroje, jako je očistit. Je však třeba zajistit pro rozdělování určité prostředky pro detekci poškození v důsledku přepsání paměti, více osvobodí, a tak dále.
Minimalizace Anomálie
Rozdělovač nakonfigurován pomocí výchozího nastavení by mělo fungovat dobře v celé řadě reálných zátěží, které silně závisí na dynamické přidělování – okenních sady nástrojů, GUI aplikací, překladače, tlumočníky, vývojové nástroje, sítě (pakety) intenzivní programy, graficky náročných obalů, webových prohlížečů, řetězec zpracování aplikace, a tak dále.

Paul Wilson a jeho kolegové psali vynikající průzkum dokument o přidělení technik, které popisuje některé z těchto cílů podrobněji. Viz Paul R. Wilson, Mark S. Johnstone Michael Neely a David Boles, “ Dynamické přidělování paměti: průzkum a kritické aktualizace” v mezinárodní seminář na správě paměti, v září 1995 (k dispozici také přes ftp). (Všimněte si, že verze mého rozdělovač, který popisují, není nejaktuálnější, kdo zklamal.)

Jak se diskutovat, je třeba minimalizovat prostor tím minimalizovat plýtvání (obecně kvůli fragmentaci) je hlavním cílem v každém rozdělovač.

Pro extrémní příklad, mezi nejrychlejší verze malloc () je ten, který alokuje vždy opatřen příštím pořadovým paměťové místo v systému k dispozici, a odpovídající nejrychlejší verzi free () je ne-op. Nicméně, taková implementace je stěží přijatelný: způsobí program spustit z paměti rychle, protože to nikdy uvolňuje nevyužitý prostor. Úbytků u některých rozdělování používaných v praxi to může být téměř extrémní za určitých zatížení. Jako Wilson rovněž konstatuje, můžete plýtvání měřit měnově: globálně posuzováno, špatné systémy přidělování nákladů lidé snad i miliardy dolarů v paměťových čipů.

Zatímco časoprostorové otázky dominují, sada kompromisů a kompromisů je téměř nekonečný. Zde jsou jen některé z mnoha příkladů:

Vstřícný nejhorší možné zarovnání požadavků zvyšuje plýtvání tím, že nutí Alokátor přeskočit bytů s cílem sladit kousky.
Většina ustanovení pro dynamické tunability (např. stanovení režimu ladění) může vážně ovlivnit časové efektivity přidáním úrovně nepřímé a zvýšení počtu poboček.
Některá ustanovení určená k zachycení chyb omezit rozsah použitelnosti. Například dříve na verzi 2.6.6, bez ohledu na platformu, malloc interně zpracovány argumenty velikosti přidělení jako kdyby byly podepsány 32-bit celé číslo, a zachází s nonpositive argumenty, jako kdyby byly žádosti o velikosti nula. (Nicméně, jak V2.6.6, negativní argumenty vedou k selhání vrací null, být v souladu s normami POSIX).
Vstříc zvláštnosti jiných rozdělování zůstat plug-kompatibilní s nimi může snížit flexibilitu a výkon. Pro Nejpodivnější například některé časné verze Unixu rozdělování povoleno programátorům realloc paměti, které již byly uvolněné. Do roku 1993 jsem dovolil to z důvodu kompatibility. (Nicméně, vůbec nikdo si stěžoval, když upadlo “ funkce”).
Někteří (ale ne všechny), heuristiky, které zlepšují čas a / nebo prostor pro malé programy způsobit nepřijatelně horším časem a / nebo prostorové vlastnosti pro větší pořadů, které vládnou zatížení na typických systémů v těchto dnech.

Žádný soubor kompromisů podél těchto linek může být perfektní. Nicméně, v průběhu let, rozdělovač se vyvinul kompromisy, že většina uživatelů najít přijatelné. Hnací síly, které i nadále vliv na vývoj tohoto malloc patří:

Empirické studie malloc výkonu od jiných zemí (včetně výše uvedeného dokumentu Wilson et al, stejně jako další, které se zase citovaných). Tyto dokumenty si, že verze této malloc stále řazen jako současně jedním z nejvíce časově a prostorově úsporné paměti rozdělovač k dispozici. Nicméně, každý odhalí slabá místa a příležitosti pro další zlepšení.
Změny cílových zátěží. Povaha druhy programů, které jsou nejvíce citlivé na malloc implementace neustále mění. Pro možné, že základní příklad, paměťové vlastnosti X a jiných okenních systémů stále dominují.
Změny systémů a procesorů. Implementační detaily a jemné-tunings, které se snaží, aby se snadno optimalizovatelný kód pro typické procesory měnit v čase. Navíc operační systémy (včetně Linuxu a Solaris) si sami vyvinuli, například pro mapování paměti je občas-moudrá volba pro systém na úrovni přidělování.
Návrhy, zprávy, zkušenosti a kód od uživatelů a přispěvatelů. Kód se vyvinul s pomocí několika pravidelných dobrovolných přispěvatelů. Většina nedávných změn byly na popud lidí, kteří používají verzi dodávaný v Linuxu, a byly realizovány z velké části Wolfram Gloger pro Linux verzi a pak integrovat mnou.

Algoritmy
Dva klíčové prvky malloc algoritmus zůstala nezměněna od doby prvních verzí:

Okrajové Tagy
Kousky paměti nosit s sebou informace o velikosti pole před a po bloku. To umožňuje dvě důležité vlastnosti:

Dva sousedící nepoužité kusy mohou být splynul do jednoho většího kusu. Tím se minimalizuje počet nepoužitelných malé kousky.
Všechny kusy lze projet od všech známých dílu směrem vpřed nebo vzad.

Původní verze provedena hraniční značky přesně tímto způsobem. Novější verze vynechat přívěsu pole na kousky, které jsou v používání programu. To je samo o sobě menší trade-off: Pole se nikdy nepoužívali, zatímco kousky jsou aktivní, takže nemusí být přítomen. Jejich odstranění snižuje režijní náklady a plýtvání. Nicméně, nedostatek těchto oblastech oslabuje detekci chyb trochu tím, že znemožňuje zjistit, jestli uživatelé omylem přepsat pole, které by měly známé hodnoty.
binning
Dostupné kousky jsou udržovány do odpadkového koše, seskupeny podle velikosti. Existuje překvapivě velký počet (128), s pevnou šířkou koše, přibližně logaritmicky rozmístěných ve velikosti. Koše na velikosti menší než 512 bajtů každý platí pouze přesně jednu velikost (8 bajtů rozmístěny od sebe, zjednodušení výkonu 8 bajtů zarovnání). Vyhledá dostupné kousky jsou zpracovány v nejmenší prvním, nejlepší fit pořadí. Jak vyplývá z Wilson et al, best-fit (schémata různých typů a aproximací) mají tendenci produkovat nejméně fragmentace na skutečné zatížení ve srovnání s ostatní obecné přístupy, jako první-fit.

Až do verze vydána v roce 1995, byly kousky vlevo netříděný do košů, aby se nejlépe hodí strategie byla pouze orientační. Novější verze namísto seřadit kusy o velikosti v zásobnících, vztahující se vystřídá s nejstarší-první pravidlo. (Toto bylo děláno po zjištění, že menší časovou investici stálo to za to, aby se zabránilo špatné zjištěné případy.)

To znamená, že obecné kategorizace tohoto algoritmu je nejlepší nejprve splývání: Freed kousky jsou splynul se sousedními ty, a držel v zásobnících, které jsou vyhledávány dle velikosti.

Tento přístup vede k pevné účetnictví režie za kus. Vzhledem k tomu, jak informace o velikosti a bin odkazy musí konat ve všech dostupných bloku, nejmenší allocatable kus je 16 bajtů v systémech s 32bitovými ukazateli a 24 bajtů v systémech s 64bitovými ukazatelů. Tyto minimální rozměry jsou větší než většina lidí by chtěla vidět – mohou vést k významnému plýtvání například v aplikacích přidělování mnoho drobných odkazované seznam uzlů. Nicméně, 16 bajtů minimální alespoň je vlastností každého systému vyžaduje 8 bajtů zarovnání, ve kterém je každý malloc účetnictví nad hlavou.

Tento základní algoritmus může být, že je velmi rychle. I když spočívá na vyhledávací mechanizmus pro snadné najít nejlepší uložení, použití indexování techniky, využití zvláštních případech a pečlivé kódování vedou k průměrné případech vyžaduje pouze několik desítek instrukcí, samozřejmě v závislosti na počítači a přidělování vzor.

Zatímco splývání přes hranice značek a přizpůsobením přes binningu představují hlavní myšlenky algoritmu, další úvahy vedou k řadě heuristických zlepšení. Patří mezi ně lokalitu uchování, ochranu divoké přírody, mapování paměti a ukládání do mezipaměti.
Lokalita zachování
Bloky přidělené uo stejném čase program mívají podobné referenční vzory a souběžně se vyskytující životů. Udržování lokalitu minimalizuje chyby stránky a výpadků, které mohou mít dramatický účinek na výkon na moderních procesorů. Pokud lokalita byla jediná branka, může rozdělovač vždy zařadí každý postupný kus co nejblíže k předchozímu jak je to možné. Nicméně, toto může nejbližšího fit (často se přiblížil další-fit) strategie vést k velmi špatné fragmentace. V současné verzi malloc, je verze next-fit použít pouze v omezeném kontextu, který udržuje lokalitu v těch případech, kdy to není v rozporu také s dalšími cíli: Je-li kus o přesné požadované velikosti není k dispozici, nejnověji split-off prostor se používá (a resplit), je-li dostatečně velká, jinak best-fit je používán. Toto omezené použití eliminuje případy, kdy výborně použitelnou existující kus selže které mají být přiděleny, čímž se eliminuje alespoň tuto formu fragmentace. A protože tato forma next-fit je rychlejší než nejlepší fit bin-hledání, urychluje průměrnou malloc.
Wilderness Preservation
“ Poušť” (tak pojmenovaný podle Kiem-Phong Vo) kus představuje prostor sousedících s vrchní adresu přidělenou ze systému. Vzhledem k tomu, že je na hranici, to je jediný kus, který může být libovolně rozšířit (přes sbrk v UNIX), že je větší, než je (není-li ovšem sbrk se nezdaří, protože všechny paměťové vyčerpání).

Jeden způsob, jak se vypořádat s divočiny bloku je zvládnout asi stejným způsobem jako jakýkoli jiný kus. (Tato technika byla použita ve většině verzí tohoto malloc do roku 1994). I když to zjednodušuje a urychluje provádění, bez péče může vést k velmi špatné nejhorších prostor charakteristiky: Mezi další problémy, které se poušť kus se používá při další dostupné kus neexistuje, můžete zvýšit šance, že i pozdější žádosti způsobí jinak zabránit sbrk.

Lepší strategií je v současné době používají: léčbě divočiny kus jako “” větší než všechny ostatní, protože to může být tak (až do systémových omezení) a použít jej jako takový v tom nejlepším prvním skenování. To má za následek v divočině bloku vždy používá pouze tehdy, pokud žádný jiný kus neexistuje, dále vyhýbat lze předcházet fragmentaci.

Mapování paměti

Kromě rozšíření obecné účelové přidělování regionům prostřednictvím sbrk, většina verzí systému podpory Unixu volání jako mmap, že přidělí samostatný non-sousedící oblast paměti pro použití programu. To poskytuje druhá možnost v rámci malloc pro uspokojování požadavek na paměť. Vyžádání a vrácení mmaped kus může dále snížit následný fragmentaci, protože povolený mapa paměti nevytváří díru “”, které by měly být řízeny. Nicméně, protože ve vestavěné omezení a režijní náklady spojené s mmap, je to jen stojí za to dělat to ve velmi omezených situacích. Například, ve všech současných systémů, musí být mapované oblasti strana zarovnány. Také vyvolání mmap a mfree je mnohem pomalejší než vybojovat existující kus paměti. Z těchto důvodů je aktuální verze malloc spoléhá na mmap pouze tehdy, pokud (1) Žádost je větší než (dynamicky nastavitelná) prahovou velikost (v současné době standardně 1 MB) a (2) požadované místo není již k dispozici ve stávajícím aréně takže by měl být získán přes sbrk.

Z části proto, že mmap není vždy použitelná ve většině programů, aktuální verze malloc také podporuje stříhání hlavní arény, která dosahuje jednoho z účinků mapování paměti – uvolnění nepoužívané místo zpět do systému. Když dlouhověké programy obsahují krátké špičky, kde se přidělují velké množství paměti, následuje delší údolí, kde se mají skromnější požadavky na výkon systému jako celku lze zlepšit tím, že uvolňuje nevyužité části divočiny bloku zpět do systému. (Téměř ve všech verzích Unixu, můžete použít sbrk s negativními argumenty k dosažení tohoto efektu.) Uvolnění prostoru umožňuje základní operační systém, jak snížit požadavky na odkládací prostor a opakované použití tabulek mapování paměti. Nicméně, jak s mmap, můžete hovor sám být drahé, je to jen pokus, pokud koncové nevyužité paměti překročí práh laditelnou.

Caching

V nejvíce přímočaré verze základního algoritmu, je každý kus uvolní okamžitě splynul se sousedy tvořit co největší nevyužitý kus. Stejně tak kousky vytvořil (tím, že rozdělí větší kousky), pouze je-li výslovně požadováno.

Operace se rozdělit a spojovat kousky nějakou dobu trvat. Tentokrát režie může být někdy zabránit pomocí některé z obou dvou ukládání do mezipaměti strategií:

Odložená Koalescenční

Spíše než splývání uvolněné kusy, nechat na své stávající velikosti v naději, že další žádost o stejné velikosti se přijdou brzy. To šetří splývají, později rozdělena, a čas to bude trvat najít non-přesně-designu kus rozdělit.
Preallocation
Spíše než rozdělení z nové kusy jeden po druhém, pre-split mnoho najednou. To je zpravidla rychlejší než dělat to jeden-at-a-time.

Vzhledem k tomu, že základní datové struktury v rozdělovač povolení splývání kdykoli v některém z malloc, free, nebo realloc, odpovídající caching heuristiky jsou snadno použitelné.

Účinnost ukládání do mezipaměti samozřejmě závisí na nákladech na štěpení, splývání a vyhledávání ve vztahu k práci, potřebné pro sledování mezipaměti kousky. Navíc účinnost menší samozřejmě závisí na politice použité při rozhodování, kdy do mezipaměti proti shlukování je. .

Ukládání může být dobrý nápad, v programech, které neustále přidělit, a uvolnit kusy pouhých několika velikostech. Například, pokud si napsat program, který přiděluje a uvolňuje mnoho uzlů stromu, můžete se rozhodnout, že stojí za to do mezipaměti některé uzly, za předpokladu, že víte o rychlý způsob, jak to udělat. Nicméně, bez znalosti programu mohou malloc nevím, jestli by to byl dobrý nápad splynout mezipaměti malé kousky, aby bylo možné uspokojit větší požadavek nebo zda větší požadavek je třeba vzít někde jinde. A to je obtížné pro rozdělovač činit informovanější dohady o této záležitosti. Například, to je stejně nákladné pro rozdělovač zjistit, kolik celkem souvislé místo by získal tím, že spojuje kousky jako by to bylo jen splývat, a pak resplit je.

Předchozí verze rozdělovač použít několik search-objednávání heuristice, kterou provedla přiměřené odhady o ukládání do mezipaměti, i když s občas špatné nejhorších výsledků. Ale v čase, tato heuristika se zdají být stále méně efektivní pod skutečnými zátěžemi. To je pravděpodobně proto, že skutečné programy, které jsou silně závislé na malloc stále větší tendenci používat větší škálu Chunk velikostí. Například, v jazyce C + + programy, pravděpodobně odpovídá trendu programy používat větší počet tříd. Různé třídy mívají různé velikosti.

[Poznámka: Novější verze malloc DO paměť, ale jen malé kousky.]
Lookasides

Zbývá jedna druhů cache, která je vysoce žádoucí v některých aplikacích, ale není implementována v této rozdělovač – lookasides pro velmi malé kousky. Jak již bylo zmíněno výše, základní algoritmus ukládá minimální velikost bloku, který může být velmi nehospodárné pro velmi malé požadavky. Například spojový seznam v systému s 4-byte ukazatele rozdělit uzly hospodářství zařazují pouze, řekněme, dva ukazatele, které vyžadují pouze 8 bytů. Vzhledem k tomu, minimální velikost bloku je 16 bajtů, uživatelské programy přidělování pouze seznam uzlů trpí 100% zatížení.

Odstranění tohoto problému při zachování přenosný vyrovnání by vyžadovalo, aby rozdělovač neukládá režii. Techniky jejím uskutečněním existují. Například může kusy být kontrolovány s cílem zjistit, zda patří do většího prostoru agregované přes adresu srovnání. Nicméně, tím způsobí významné náklady, ve skutečnosti náklady by bylo nepřijatelné, v tomto rozdělovač. Bloky jsou jinak sledovány adresu, takže pokud svévolně omezen, může kontrola vede k náhodným vyhledávání prostřednictvím paměti. Navíc podpora vyžaduje přijetí jednoho nebo více politiky kontroly toho, zda a jak vůbec splývají malé kousky.

Tyto problémy a omezení vedou k jednomu z mála druhů situací, v nichž programátoři by měli pravidelně psát své vlastní speciální účel rutiny pro správu paměti (tím, že například v C + + Přetěžování operator new ()). Programy spoléhají na velké, ale přibližně znám počet velmi malých kouscích může najít to výhodné budovat velmi jednoduché alokátory. Například, mohou být přiděleny kusy z pevné pole s vloženým freelist, spolu s ustanovením spoléhat na malloc jako záloha v případě, že pole vybije. Poněkud flexibilně, mohou být založeny na C nebo C + + verze obstack dispozici s GNU gcc a libg + +.

Comments are closed.