Obsah
1. Když všechny další pokusy o zvýšení výkonnosti selžou…
2. Virtuální adresa vs. fyzická adresa, stránkování
4. Vliv velikosti stránek na výkonnost systému
5. Řešení problému: large (huge) pages
6. Podpora large (huge) pages v JVM
7. Povolení large pages v operačním systému (1.část)
8. Obsah následující části seriálu
1. Když všechny další pokusy o zvýšení výkonnosti selžou…
V předchozích částech seriálu o monitorování a správě paměti, které je možné použít v (Open)JDK 6 a samozřejmě i v (Open)JDK 7, jsme si řekli základní informace o funkci správců paměti (GC – Garbage Collectors) i o tom, jakým způsobem je alokována halda (heap) i jednotlivé oblasti a podoblasti, na něž je halda z důvodů efektivnější správy paměti rozdělena. Ovšem na problémy s výkonností aplikací psaných v Javě můžeme narazit i v těch případech, pokud je aplikace naprogramována optimálně, je zvolen ten nejlepší dostupný typ správce paměti (samozřejmě volaný s ověřenými parametry) a i halda je rozdělena na základě údajů zjištěných při testovacích bězích aplikace. Poněkud paradoxně se mohou výkonnostní problémy objevit na těch nejlepších počítačových systémech vybavených několika procesory a mnoha gigabajty či desítkami gigabajtů operační paměti. Celkem logicky si můžeme položit otázku, zda je za těchto okolností vůbec možné ještě něco vylepšit?
Samozřejmě, že vždy je možné ještě něco vylepšit či poupravit :-), zejména tehdy, když si uvědomíme, že výkonnostní problém může v mnoha případech souviset opět se správou paměti, tentokrát ovšem se správou, kterou do značné míry automaticky provádí samotný mikroprocesor (či mikroprocesory u multiprocesorových systémů) v součinnosti s operačním systémem. Prakticky všechny moderní procesorové architektury i operační systémy totiž podporují virtualizaci paměti, s čímž ovšem souvisí i to, že se přístup do paměti stal poněkud složitější a obecně taktéž pomalejší než na architekturách, kde platí rovnost virtuální_adresa=fyzická_adresa. Důvod, proč tomu tak je, si stručně vysvětlíme v navazující kapitole. Upozornění: v dalším textu se nebudu zabývat triviálně zjistitelným problémem souvisejícím s virtualizací paměti, konkrétně se stavem, kdy je systém pomalý z toho důvodu, že neustále odkládá a načítá stránky ze swapovacího souboru. Zde by bylo řešení následující: úprava velikosti haldy a taktéž velikosti oblastí na haldě, pečlivý výběr správce paměti, použití komprimovaných ukazatelů a samozřejmě též pokus o nalezení slabých míst v aplikaci.
2. Virtuální adresa vs. fyzická adresa, stránkování
Pro začátek si stručně (a v rámci stručnosti i poněkud nepřesně) popišme, jakým způsobem je vlastně paměť spravována na moderních mikropočítačových architekturách. Předpokládejme, že si nějaká aplikace vyžádala od operačního systému pomocí funkce malloc() například oblast o velikosti 100kB. Operační systém skutečně tuto oblast zarezervuje (zde musí spolupracovat s jednotkou MMU mikroprocesoru) a vrátí aplikaci ukazatel na první volný bajt v nově alokované paměťové oblasti. Aplikaci se sice celá stokilobajtová oblast jeví jako kontinuální, ovšem ve skutečnosti je tato oblast na fyzické úrovni rozdělena do takzvaných stránek, které mohou být ve fyzické paměti rozmístěny prakticky libovolně (čím déle systém běží, tím k větší fragmentaci paměťového prostoru dochází) – dokonce je možné, že některé z méně často používaných stránek budou uloženy do swapovacího oddílu nebo do swapovacího souboru, tj. nebudou vůbec uloženy ve fyzickém paměťovém modulu.
V praxi to tedy znamená, že virtuální adresy jsou obecně odlišné od fyzických adres, tj. od adres, které jsou mikroprocesorem posílány po adresové sběrnici ve chvíli čtení či zápisu slova z/do paměťového modulu. Dokonce se mohou (a na mnoha systémech i budou) lišit i logické adresy používané v aplikacích od adres virtuálních, ovšem to nesouvisí přímo se stránkováním, ale se segmentací paměti (přesněji řečeno spodních n bitů virtuální a fyzické adresy bude totožných, přičemž n souvisí s velikostí stránek). Navíc musí mít mikroprocesor a současně i operační systém ve vhodné datové struktuře uloženy všechny potřebné informace o stránkách – na jakou adresu je každá stránka mapována ve fyzické paměti, zda je stránka uložena ve swapovacím souboru/oddílu atd. Postupem času se tyto údaje začaly ukládat do datových struktur složených z několika hierarchicky uspořádaných tabulek – u architektury x86 se jedná o dvouúrovňovou hierarchii a v případě použití PAE o hierarchii tříúrovňovou (na 64bitových systémech je to pro jistotu ještě o něco složitější :-).
3. Význam TLB
Přepočet, či možná přesněji řečeno mapování mezi virtuální a fyzickou adresou musí být prováděno při čtení či zápisu každého bajtu, samozřejmě i při načítání operačních kódů instrukcí tvořících běžící program, ukládání dat na zásobník atd. Z toho nutně vyplývá, že mapování adres musí být velmi rychlé, protože i zdržení o jeden či dva takty by se negativně projevilo na výkonnosti celého systému. Vzhledem k tomu, že procházení hierarchie tabulek (i když je vykonáváno mikroprocesorem resp. jeho modulem MMU) není tak rychlé, jsou všechny moderní procesory vybaveny takzvaným TLB neboli Translation Lookaside Buffer. Jedná se vlastně o velmi rychlou cache paměť implementovanou přímo na mikroprocesoru, v níž jsou uloženy nejčastěji používané informace z tabulek stránek. Pokud je při čtení či zápisu bajtu na virtuální adresu nalezeno v TLB příslušné mapování, je vše v pořádku – virtuální adresa se převede na adresu fyzickou.
Problém nastane, pokud se daný vzorek (konkrétně nejvyšších n bitů) virtuální adresy v TLB nenachází. Zde již musí mikroprocesor skutečně projít celou hierarchií tabulek stránek, což je z časového hlediska poněkud náročné, zejména v případě, že je stránek několik desítek tisíc, což se poměrně snadno stane, jak si ostatně ukážeme v následující kapitole. Pokud se záznam najde, je ihned uložen do TLB, z něhož ovšem musí být některý méně často používaný záznam vymazán (konkrétní strategie ovládání této paměti cache je před námi ovšem skryta v mikroprocesoru). V případě, že ani po projití celé hierarchie není potřebný mapovací záznam nalezen, je mikroprocesorem vyvolána výjimka, kterou může operační systém obsloužit například tak, že příslušnou stránku (jejíž virtuální adresu má samozřejmě k dispozici) přenese ze swapovacího souboru do fyzické paměti a opět vloží příslušný záznam do TLB.
4. Vliv velikosti stránek na výkonnost systému
Vraťme se nyní do druhé poloviny osmdesátých let minulého století, kdy firma Intel uvedla na trh mikroprocesory Intel 80386. Jedná se o první mikroprocesory řady x86, které měly implementovanou stránkovací jednotku a v instrukční sadě i systému výjimek plně podporovaly stránkování. V té době byly v běžných počítačích instalovány paměti o kapacitách v řádu megabajtů, od klasického jednoho megabajtu běžného i u starších počítačů s procesorem Intel 80286 přes poměrně obvyklé 4MB (minimum pro spuštění Doomu :-) až do osmi megabajtů, což byla maximální dosažitelná kapacita na počítačích s osmi jednomegabajtovými 30pinovými moduly SIMM (tyto moduly byly osmibitové, proto se musely u počítačů s 32bitovými procesory 80386DX instalovat vždy po čtveřicích). Teoretická maximální kapacita 30pinového modulu SIMM je sice 16 MB, ovšem moduly s takto velkou kapacitou nebyly zpočátku k dispozici – typické kapacity byly 256 kB a 1 MB.
Vzhledem k tomu, že velikosti stránek měly být dostatečně malé kvůli redukci času nutného pro uložení či načtení stránek ze swapovacího souboru a taktéž kvůli tomu, aby se zbytečně nealokovala stránka zaplněná jen z několika procent, zvolila firma Intel za výchozí velikost stránky hodnotu 4kB, tj. 4096 bajtů. Celkový počet stránek se tedy v případě tehdy používaných kapacit operačních pamětí pohyboval v celkem rozumných mezích. Například u počítače s 4MB paměti se jednalo o 4MB/4kB=1024 stránek (zde se dopouštíme drobné nepřesnosti), což znamenalo, že i TLB s relativně malou kapacitou paměti cache měl poměrně velkou úspěšnost (hit rate) při vyhledávání stránek, do kterých se četly či zapisovaly bajty. Navíc je dobré si uvědomit, že při tehdejších rychlostech procesorů, kapacitách RAM a v neposlední řadě i „kvalitě“ operačních systémů se prakticky nespouštělo velké množství paralelně běžících úloh současně, což při poměrně dobré lokalitě dat u jedné úlohy znamenalo, že se úspěšnost TLB opět poměrně významně zvýšila.
Celá situace je znázorněna na tomto schématu umístěného na Wikipedii.
5. Řešení problému: large (huge) pages
Po uvedení mikroprocesoru Intel 80386 na trh se podpora pro stránkování stala prakticky nedílnou součástí všech novějších operačních systémů, které pro tuto platformu vznikly. Ovšem od uvedení procesoru 80386 už uběhlo 25 let a za tu dobu vzrostly běžně používané kapacity operačních pamětí na tisícinásobek (!) a současně se i zvýšil průměrný počet úloh, které jsou v systému paralelně provozovány. To mělo značný negativní dopad na účinnost TLB, který se sice taktéž neustále vylepšoval a zvyšoval svoji kapacitu paměti cache (která musí být typu CAM, tj. jde o asociativní paměť), ovšem i přes toto vylepšování byl každý výpadek TLB se zvyšující se kapacitou paměti mnohem horší – procesor totiž musel v této chvíli prohledávat tabulky stránek, které již dnes neobsahují jen 1024 stránek jako tomu bylo u 4MB RAM, ale mnohdy i několik milionů stránek. Výrobci procesorů a ve druhém kole i vývojáři operačních systémů tedy hledali nějakou cestu, jak tento problém uspokojivě vyřešit.
Použité řešení je vlastně celkem jednoduché – kromě dnes již klasických stránek o velikosti 4 kB podporují novější procesory i stránky mnohem větší, které mají například velikost 2MB nebo 4MB. Oba dva typy paměťových stránek mohou být použity společně (myšleno na jednom systému), protože všechny datové struktury (hierarchické tabulky stránek) zůstávají zachovány – jedinou novou informaci představuje příznakový bit uložený v tabulkách stránek, který určuje, zda se jedná o původní čtyřkilobajtovou stránku nebo o stránku „velkou“ (huge, large). Přes služby operačního systému je možné zažádat o alokaci libovolného počtu velkých stránek, důležité je ovšem to, že ne vždy se alokace povede, a to i v případě, že je k dispozici dostatek operační paměti. Důvod, proč se někdy alokace nepovede, je pochopitelný – alokovaná stránka musí být ve fyzické paměti umístěna na kontinuálních adresách (tj. jedná se o jeden blok o délce 2MB nebo 4MB), tento blok se však v případě fragmentované paměti systému nepodaří nalézt. Z tohoto důvodu je nejlepší zažádat o alokaci velkých stránek ihned po startu systému nebo dokonce i v průběhu startování inicializačních skriptů.
6. Podpora large (huge) pages v JVM
Nyní se dostáváme k podpoře velkých stránek v JVM. Tato technologie je podporována již od JDK 5.0 a dá se zapnout přepínačem -XX:+UseLargePages, například následovně při spouštění testu, jehož zdrojový kód byl uveden minule:
java -XX:+UseLargePages ConcurrentConcatenationTest
V případě, že se paměť pro haldu (a nejenom pro ni) nepodařila naalokovat přes velké stránky, nejedná se o zásadní chybu a aplikace bude pokračovat v běhu (i když s cca o 10 až 15 procent menším výkonem). Ovšem na standardní výstup (ne na výstup chybový!) se vypíše podobné varování:
OpenJDK Server VM warning: Failed to reserve shared memory (errno = 5).
popř.:
OpenJDK Server VM warning: Failed to reserve shared memory (errno = 22).
Pokud se toto hlášení objeví, je možné, že velkých stránek není dostatečné množství (JVM buď použije malé stránky nebo stránky velké, nikdy ne jejich mix), že jsou již zaalokované jiným procesem, nebo (což je na nenakonfigurovaných systémech pravděpodobné) není podpora pro velké stránky zapnuta.
7. Povolení large pages v operačním systému (1.část)
Nejprve se musíme přesvědčit, jestli daná platforma, tj. jak mikroprocesor, tak současně i jádro operačního systému, podporuje práci s velkými stránkami. To můžeme zjistit více způsoby, pravděpodobně nejjednodušeji následovně:
grep Huge /proc/meminfo
Pokud platforma velké stránky podporuje (i když není ani jedna naalokována), vypíšou se čtyři řádky (u nichž se číselné údaje samozřejmě mohou lišit):
HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 Hugepagesize: 2048 kB
Dále je již možné velké stránky naalokovat. Pokud například potřebujeme na systému, jehož velké stránky mají velikost 2MB, použít aplikaci s velikostí haldy 20MB, postačuje naalokovat 10 velkých stránek (jedná se pouze o příklad, ve skutečnosti bude halda spíše stokrát větší a bude tedy potřebovat stokrát více velkých stránek). Následující příkaz se musí spouštět pod rootem:
echo 10 > /proc/sys/vm/nr_hugepages
O tom, zda alokace skutečně proběhla, se můžeme přesvědčit zpětným výpisem pseudosouboru:
cat /proc/sys/vm/nr_hugepages 10
a samozřejmě též výše uvedeným příkazem:
grep Huge /proc/meminfo HugePages_Total: 10 HugePages_Free: 10 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB
Poznámka: výše uvedený postup je vhodný spíše pro testování. Pokud se mají velké stránky alokovat automaticky při startu systému, lze provést změnu v souboru /etc/sysctl.conf, například přidáním následujícího řádku:
vm.nr_hugepages = 10
Nezávisle na způsobu specifikace celkového počtu velkých stránek je dobré si uvědomit, že JVM buď bude alokovat haldu pouze s využitím velkých stránek nebo ji naopak bude alokovat s využitím běžných malých čtyřkilobajtových stránek. To znamená, že by celková kapacita rezervovaných velkých stránek (tj. jejich počet×kapacita) měla přesahovat velikost haldy specifikovanou pomocí přepínače -Xmx, jinak bude výsledek celého snažení spíše kontraproduktivní.
8. Obsah následující části seriálu
Další postup při nastavování velkých stránek je již do určité míry závislý na typu operačního systému a taktéž na tom, jaká JDK se používá (Oracle JDK, OpenJDK, IBM JDK atd.). Každý typ JDK totiž může k velkým stránkám přistupovat odlišným způsobem – buď přes sdílenou paměť nebo přes virtuální souborový systém. Z důvodu velké rozsáhlosti se problematikou dalšího nastavení a především měření výkonnosti Javovských aplikací využívajících velké stránky, budeme věnovat ještě v následující části tohoto seriálu.
9. Odkazy na Internetu
- HugePages
http://linux-mm.org/HugePages - Tuning big java heap and linux huge pages
http://www.tikalk.com/alm/forums/tuning-big-java-heap-and-linux-huge-pages - How do I set up hugepages in Red Hat Enterprise Linux 4
http://magazine.redhat.com/2007/05/29/how-do-i-set-up-hugepages-in-red-hat-enterprise-linux-4/ - Java SE Tuning Tip: Large Pages on Windows and Linux
http://blogs.sun.com/dagastine/entry/java_se_tuning_tip_large - Translation lookaside buffer
http://en.wikipedia.org/wiki/Translation_lookaside_buffer - Physical Address Extension
http://en.wikipedia.org/wiki/Physical_Address_Extension - Java HotSpot VM Options
http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html - Amdahl's law
http://en.wikipedia.org/wiki/Amdahl_law - Garbage collection (computer science)
http://en.wikipedia.org/wiki/Garbage_collection_(computer_science) - Dr. Dobb's | G1: Java's Garbage First Garbage Collector
http://www.drdobbs.com/article/printableArticle.jhtml?articleId=219401061&dept_url=/java/ - Java's garbage-collected heap
http://www.javaworld.com/javaworld/jw-08–1996/jw-08-gc.html - Compressed oops in the Hotspot JVM
http://wikis.sun.com/display/HotSpotInternals/CompressedOops - 32-bit or 64-bit JVM? How about a Hybrid?
http://blog.juma.me.uk/2008/10/14/32-bit-or-64-bit-jvm-how-about-a-hybrid/ - Compressed object pointers in Hotspot VM
http://blogs.sun.com/nike/entry/compressed_object_pointers_in_hotspot - Java HotSpot™ Virtual Machine Performance Enhancements
http://download.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html - Using jconsole
http://download.oracle.com/javase/1.5.0/docs/guide/management/jconsole.html - jconsole – Java Monitoring and Management Console
http://download.oracle.com/javase/1.5.0/docs/tooldocs/share/jconsole.html - Great Computer Language Shootout
http://c2.com/cgi/wiki?GreatComputerLanguageShootout - x86–64
http://en.wikipedia.org/wiki/X86–64 - Physical Address Extension
http://en.wikipedia.org/wiki/Physical_Address_Extension - Java performance
http://en.wikipedia.org/wiki/Java_performance - 1.6.0_14 (6u14)
http://www.oracle.com/technetwork/java/javase/6u14–137039.html?ssSourceSiteId=otncn - Update Release Notes
http://www.oracle.com/technetwork/java/javase/releasenotes-136954.html - Java virtual machine: 4.10 Limitations of the Java Virtual Machine
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#88659 - Java™ Platform, Standard Edition 7 Binary Snapshot Releases
http://dlc.sun.com.edgesuite.net/jdk7/binaries/index.html - Trying the prototype
http://mail.openjdk.java.net/pipermail/lambda-dev/2010-August/002179.html - Better closures (for Java)
http://blogs.sun.com/jrose/entry/better_closures - Lambdas in Java: An In-Depth Analysis
http://www.infoq.com/articles/lambdas-java-analysis - Class ReflectiveOperationException
http://download.java.net/jdk7/docs/api/java/lang/ReflectiveOperationException.html - Proposal: Indexing access syntax for Lists and Maps
http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/001108.html - Proposal: Elvis and Other Null-Safe Operators
http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/000047.html - Java 7 : Oracle pushes a first version of closures
http://www.baptiste-wicht.com/2010/05/oracle-pushes-a-first-version-of-closures/ - Groovy: An agile dynamic language for the Java Platform
http://groovy.codehaus.org/Operators - Better Strategies for Null Handling in Java
http://www.slideshare.net/Stephan.Schmidt/better-strategies-for-null-handling-in-java - Control Flow in the Java Virtual Machine
http://www.artima.com/underthehood/flowP.html - Java Virtual Machine
http://en.wikipedia.org/wiki/Java_virtual_machine - ==, .equals(), compareTo(), and compare()
http://leepoint.net/notes-java/data/expressions/22compareobjects.html - New JDK7 features
http://openjdk.java.net/projects/jdk7/features/ - Project Coin: Bringing it to a Close(able)
http://blogs.sun.com/darcy/entry/project_coin_bring_close - ClosableFinder source code
http://blogs.sun.com/darcy/resource/ProjectCoin/CloseableFinder.java - Joe Darcy blog about JDK
http://blogs.sun.com/darcy - Java 7 – more dynamics
http://www.baptiste-wicht.com/2010/04/java-7-more-dynamics/ - ArrayList (JDK 1.4)
http://download.oracle.com/javase/1.4.2/docs/api/java/util/ArrayList.html