garoeda | mbligh: ik ga vanavond vertellen over NUMA machines en de 2.6 kernel |
---|---|
garoeda | om deze presentatie binnen de perken te houden qua tijd ga ik enkele dingen vereenvoudigen, niet echt belangrijke dingen maar als je vindt dat ik toch belangrijke dingen zou overslaan kan je het gerust vragen |
garoeda | NUMA = Non Uniform Memory Architecture |
garoeda | dit is het geval wanneer we een SMP machine hebben met niet uniforme karakteristieken : geheugen, cpu's en IO bussen bevinden zich niet op dezelde afstand (not equally spaced) van elkaar |
garoeda | de reden hiervoor is dat het zeer duur is om een uniforme SMP machine te bouwen met meer dan 4 cpu's |
garoeda | wanneer je een systeem maakt dat zo groot is moet je de systeembussen vertragen om met de afstand en grootte overweg te kunnen |
garoeda | je kan een grotere en snellere machine maken wanneer je sommige verzamelingen resources dichter bij elkaar plaatst dan sommige anderen |
garoeda | wanneer je dit niet doet verkrijg je een machine waar _alles_ trager wordt ipv maar enkele resources |
garoeda | we definieren zulke groepen als "nodes", een typische machine heeft 4 cpu's per node, wat geheugen en IO bussen |
garoeda | per node |
garoeda | er zijn nu nieuwere architecturen, zoals AMD's x86_64, die 1 cpu per node hebben en lokaal geheugen voor iedere cpu |
garoeda | hierdoor hebben we NUMA systemen voor een veel lagere prijs, 2000 $ ongeveer |
garoeda | en veel meer interesse in deze technologie vanuit de markt |
garoeda | dikwijls, machines die maar een beetje non uniform zijn (een beetje NUMA) worden verkocht als SMP voor de eenvoudigheid |
garoeda | grote machines van bedrijven zoals SGI hebben nu 512 CPUs of zelfs meer |
garoeda | het kan misschien helpen om zulke machine voor te stellen als een een groep van standaard SMP machines die verbonden zijn via een heel snel interconnectienetwerk |
garoeda | bijna zoals een netwerkverbinding, met het verschil dat de transfers over die bus transparant zijn voor het OS |
garoeda | en inderdaad, enkele vroege systemen waren zo gemaakt, de oudere Sequent NUMA-Q gebruikt een standaard 450 NX 4x chipset, met een "magic" NUMA interface die in de systeembus is geplugd van iedere node om ze met elkaar te verbinden en verkeer door te geven |
garoeda | de traditionele meetwijze om te bepalen hoe NUMA (hoe non uniform) een machine is: is het delen van van memory latency van het lokale geheugen door de latency van het remote geheugen |
garoeda | bv, wanneer ik een lokale geheugenaccess doe (geheugen op dezelfde node als de cpu) in 80 ns en een remote geheugentoegang (cpu op een andere node dan het geheugen) in 800ns dan drukt men de ration uit als 10:1 |
garoeda | spijtig genoeg is dit een zeer onnauwkeurige beschrijving van de machine |
garoeda | en het houdt geen rekening met enkele belangrijke factoren, zoals de bandbreedte van het interconnectienetwerk |
garoeda | eens dat netwerk begint verzadigd te worden kan de 800 ns snel stijgen naar 8000 ns |
garoeda | het is dus belangrijk om de geheugentoegang zoveel mogelijk lokaal te houden |
garoeda | men stelt ons dikwijls de vraag waarom mensen geen cluster van kleinere machines gebruiken in plaats van een grote NUMA machine |
garoeda | en inderdaad, dat zou veel goedkoper zijn, voor evenveel cpu rekenkracht |
garoeda | spijtig genoeg maakt dit het werk van de applicaties zwaarder, alle intercommunicatie en load balancing moet nu explicieter gedaan worden en wordt dus meer complex |
garoeda | sommige grote applicaties (bv database servers) kunnen niet eenvoudig gesplitst worden in verschillende cluster nodes |
garoeda | in dit soort situaties gebruiken de mensen meestal NUMA machines |
garoeda | een andere leuke eigenschap van het gebruik van een NUMA machine is dat load balancing problemen enzoverer in het OS worden opgelost , ipv iedere keer opnieuw in elke applicatie |
garoeda | we verhuizen ook de hardware kennis naar het OS waardoor de applicaties meer portable worden |
garoeda | er zijn verschillende niveaus waarop we NUMA ondersteuning kunnen hebben |
garoeda | 1) doe alsof je op SMP werkt en negeer het lokaale geheugen en de NUMA karakteristieken |
garoeda | 2) impliciete ondersteuning: het OS probeert zoveel mogelijk om lokale resources te gebruiken en groepeert applicaties en en de benodige resources zo dicht mogelijk bij elkaar |
garoeda | 3) expliciete ondersteuning: geef userspace een API waar applicaties in een abstracte vorm aan het OS kunnen vertellen wat ze willen |
garoeda | in de linux 2.6.0 kernel staan we aan het begin van niveau 2 en er zijn enkele rudimentaire stukken ondersteuning voor niveau 3 |
garoeda | niveau 1 werkt maar geeft geen goede performantie |
garoeda | dat is het eerste wat ik deed wanneer ik linux naar het Sequent NUMA-Q platform porte (overbracht) |
garoeda | de eerste stap in NUMA ondersteuning is proberen geheugen te alloceren dat lokaal is aan de cpu waarop de applicatie draait |
garoeda | we doen dat per default in linux wanneer de kernel __alloc_pages oproept (de hoofd geheugen allocator) |
garoeda | wanneer het geheugen in de lokale node op is gaat het automatisch geheugen van remote nodes alloceren, het is alleen wat trager |
garoeda | NUMA ondersteuning wordt geactiveerd door CONFIG_NUMA, dat afhangt van CONFIG_DISCONTIGMEM |
garoeda | alhoewel het geheugen daarom niet discontinu hoeft te zijn (zoals discontigmem suggereert), dat is historisch zo gegroeid |
garoeda | dus in plaats van 3 geheugen zones te hebben voor het systeem (bv ZONE_DMA, ZONE_NORMAL en ZONGE_HIGHMEM) krijgen we nu 3 zones per node .. alhoewel velen "leeg" eindigen (er zit geen geheugen in) |
garoeda | voor iedere fysieke pagina geheugen in een Linux systeem hebben we een 'struct page" controle entry, die we verzamelen in een array die mem_map heet, dit is 1 blok continu geheugen |
garoeda | op NUMA systemen veranderne we dit in 1 array per node (lmem_map[node]) |
garoeda | maar essentieel hebben we nog altijd 1 struct page per page physisch geheugen |
garoeda | het opsplitsen van die array biedt ons vele voordelen, 1 daarvan is eenvoud van code |
garoeda | een andere is dat we die controle structuren kunnen alloceren vanuit het lokale geheugen van de node wat de toeganstijd verbetert |
garoeda | op een typisch systeem heeft men 16 GB RAM, 4 GB per node |
garoeda | in physishce adres bereiken geeft dit : 0-4GB on node 0, 4-8GB on node1, 8-12Gb on node2, en 12-16GB on node 3 |
garoeda | op een 32 bit systeem vormt dit al een probleem, al het permantent geheugen van de kernel komt uit de eerste GB fysieke ram |
garoeda | spijtig genoeg blijkt dit moeilijk aanpasbaar te zijn, vele drivers gaan er vanuit dat fysieke adressen voor kernel geheugen (ZONE_NORMAL) in een unsigned int passen (32 bits) |
garoeda | dus we kunnen dat geheugen niet zomaar verspreiden over het systeem, dat is een performantieprobleem dat we nog altijd hebben |
garoeda | sommige dingen (zoals de lmap_arrays) worden verplaatst door een paar hacks at boottime maar de meeste structures (bv entries voor dcache en inode cache) moeten nog altijd op node 0 verblijven |
garoeda | een van de andere dingen die we doen om het verkeer tussen de nodes te beperken is ipv een globale swapd deamon (kswapd) om de pagina's terug te vragen, hebben we nu 1 deamon per node die enkel de pagina's op die node gaat scannen |
garoeda | dit geeft ons veel betere performantie en minder interconnect verkeer tijdens het terugvragen van geheugen |
garoeda | we kunne ook copieen van read-only data naar iedere node sturen |
garoeda | bv, we kunnen het kernel image naar iedere node versturen, en de cpu's op iedere node spreken dan elk hun eigen copie aan, dit verbruikt maar een beetje extra geheugen maar spaart veel interconnect verkeer uit |
garoeda | Dave Hansen heeft een patch geschreven die read-only data voor de gebruikers (bv de tekst van glibc, en programma's zoals gcc) naar iedere node copieert waardoor je dezelfde voordelen krijgt |
garoeda | dit gaf ons een 5% - 40% performantie voordeel, afhankelijk van welke andere patches we gebruikten en welke benchmark we gebruikten |
garoeda | de replicatie van read-write data zal moeilijk zijn (verschillende copien in sync houden tijdens het schrijven) en waarschijnlijk de moeite niet waard zijn |
garoeda | tot zover gaan we enkel read-only gegevens doen |
garoeda | de 2.6 kernel heeft per node LRU lijsten (least recently used lists of which memory pages have been accessed recently) |
garoeda | ipv een globale lijst |
garoeda | niet alleen geeft dit ons meer lokale toegang tot de informatie maar laat ons ook toe om pagemap_lru_lock op te splitsen |
garoeda | dit is het lock (slot) dat de LRU lijsten controleert. voor we dit opsplitsten waren we 50 % van de systeemtijd van een kernel compile bezig met het wachten op dit lock |
garoeda | wanneer dit opgesplitst was werd de wachttijd onmeetbaar klein |
garoeda | ok, dit was het meeste van de VM, nu de scheduler |
garoeda | het heeft niet veel zin om lokaal node geheugen aan een proces te geven als we dat proces direct erna migreren naar een andere node |
garoeda | dus namen we de standaard O(1) scheduler van 2.6 en veranderen dit een beetje |
garoeda | met de O(1) schedulerer, is er een runqueue van tasks (taken) per cpu |
garoeda | in normale SMP mode, gaat elke CPU enkel tasks draaien uit zijn eigen runqueue en van tijd tot tijd wisselen we eens af van runqueue |
garoeda | maar op een NUMA systeem willen we geen tasks migreren tussen de nodes waar mogelijk |
garoeda | we willen ze op de lokale node houden, om de cache en het geheugen zuiver te houden |
garoeda | dus veranderden we het standaard balancing algoritme om enkel te balanceren tussen de runqueues van de CPU's die op dezelfde node zitten |
garoeda | we gaan dus maar zelden tasks uitwisselen tussen nodes |
garoeda | echter, tijdens het exec() moment van een proces is dit anders (we gaan dat proces namelijk overschrijven met een nieuw) |
garoeda | dus op het exec moment doen we een globale node herbalancering en brengen de ge'execte taak naar de minst belaste node |
garoeda | dit doet eigenlijk veel load balancing voor ons en heeft niet direct een kostprijs |
garoeda | de code in 2.6 om NUMA scheduling te ondersteunen is echter redelijk basic en heeft nog veel werk nodig |
garoeda | mijn hoofddoel voor de nabije toekomst is om het RSS (resident set size of memory) van iedere task bij te houden op een per-node basis en globaal |
garoeda | dan kunnen we die informatie gebruiken om beter beslissingen te maken, als het het meeste geheugen van proces op node X zit zouden we dit proces naar node X kunnen verplaatsen |
garoeda | om goed te kunnen balanceren moeten we er ook rekening mee houden hoeveel cpu een taks gebruikt, het zal meer effect hebben wanneer je een task verplaatst die veel cpu gebruikt |
garoeda | maar het is "goedkoper" om het te verplaatsen wanneer het een kleine cach footprint heeft (dat we min of meer meten via RSS) |
garoeda | dus eindigen we met een "goodness" berekening voor migratie die iets is als "cpu_percentage/(remote_rss - local_rss)" |
garoeda | ok, genoeg over de scheduler, nu iets over IO |
garoeda | als we een SAN (storage area network) hebben met een IO connectie vanuit iedere node (bv met een fibrechannel switch) |
garoeda | dan heeft het zin om NUMA aware MPIO te gebruiken (multi path IO) |
garoeda | we gaan simpelweg proberen om IO traffic over de lokale interface te routen en het verkeer over dezelfde interface terug te krijgen |
garoeda | het is duidelijk dat dit veel verkeer spaart op de centrale interconnect |
garoeda | als een interface down gaat (kapot) op de lokale node kunnen we nog altijd terugvallen op een remote node IO adapater |
garoeda | echter, vele machines zoals de amd64 bv) hebben dit soort setup niet |
garoeda | in de plaats daarvan is de meeste IO verbonden met juist 1 node |
garoeda | om daar mee overweg te kunnen moeten we echt in de scheduler ingrijpen en tasks die afhankelijk zijn van IO draaien zo dicht mogelijk bij de node draaien waarvan ze de IO aanspreken |
garoeda | en de cpu gebonden tasks draaien op de andere nodes |
garoeda | daarvoor hebben we nog geen ondersteuning in Linux maar dat kan er nog bijkomen in 2.6 |
garoeda | dezelfde principes gelden zowel voor disk IO als netwerk IO |
garoeda | netwerk IO is echter wel wat ingewikkelder omdat we daar ook moeten rekening houden met de IP adressen van de machine etc |
garoeda | de overblijvende sectie die ik ga bespreken zal over de userspace api gaan |
garoeda | wat ik daarnet niveau 3 genoemd heb |
garoeda | we tonen informatie over de topology van een NUMA machine via sysfs |
garoeda | bv de groeperingen van de welke cpu's er op welke node zitten |
garoeda | we tonen ook meminfo op een per-node basis (zoals /proc/meminfo) |
garoeda | en er zijn ook mappings over welke PCI bussen er op welke node zitten |
garoeda | via meminfo kan je zien hoeveel free/gealloceerd geheugen er is per node en waaraan het gealloceerd is |
garoeda | en een "out-of-tree" verzameling patches die de gebruikers toelaat om te specifieren uit welke node er geheugen moet gealloceerd worden (nog niet af, gaat de komende maanden in orde komen) |
garoeda | Andi Kleen en Matt Dobson werken daaraan |
garoeda | we kunnen ook sys_sched_affinity dingen gebruiken van Robert Love om processen aan specifieke cpu's te binden en dus ook aan nodes |
garoeda | ok , dit is het ongeveer. vragen ? |
garoeda | vraag: verschil tussen Linux Numa en implementaties van AIX |
garoeda | ik denk dat het grootste verschil is in NUMA termen dat er in linux nog niet zoveel expliciete userspace ondersteuning is |
garoeda | het schedulen in de andere OS'en is waarschijnlijk ook wel een stuk gesofistikeerder |
garoeda | we kunnen het taks grouping nog veel verbeteren, threads van hetzelfde proces op 1 node draaien bijvoorbeeld |
garoeda | het verschil tussen Numa en SMP is dat je met SMP op physische wetten gaat stuiten die de snelheid vertragen, daar kan je dus niet veel aan veranderen |