Core pinning och NUMA: så får du ut mer av dina VM:er på hemmaservern

Redaktionens viktigaste punkter

  • NUMA innebär att varje CPU-sockel har sitt eget lokala minne – fjärråtkomst till en annan nods minne kan öka minneslatensen med 50–100 procent.
  • Core pinning binder en VMs vCPUs till fasta fysiska kärnor: varm cache, inga migrationer, förutsägbar prestanda.
  • Kartlägg alltid topologin först med lscpu och numactl –hardware – fel gissning kan bli sämre än ingen pinning alls.
  • Glöm inte emulator-trådarna: med emulatorpin flyttar du QEMU:s I/O-trådar till värdens egna kärnor så att de inte stör de pinnade vCPU:erna.
  • För de flesta VM:er gör Linux schedulern ett bra jobb – pinning lönar sig främst för latenskänsliga laster som spel, databaser och ljudbearbetning.

Core pinning och NUMA: så får du ut mer av dina VM:er på hemmaservern

När en virtuell maskin känns ryckig trots lediga CPU-resurser är boven ofta inte brist på råstyrka utan hur trådarna och minnet är placerade. På flersocklade system – och på moderna chiplet-CPU:er som AMD Ryzen och EPYC – sitter inte allt minne lika nära alla kärnor. Med core pinning och NUMA-medvetenhet styr du exakt vilka fysiska kärnor en VM får använda och ser till att dess minne allokeras lokalt. Här går vi igenom vad NUMA-noder är, varför pinning av vCPUs spelar roll, hur du hanterar emulator-trådar och vad isolcpus och nohz_full gör – samt när du kan låta schedulern sköta jobbet.

Vad är NUMA och varför spelar det roll?

NUMA står för Non-Uniform Memory Access. I ett NUMA-system är maskinen uppdelad i noder där varje nod består av processorkärnor plus det minne som sitter närmast dem – minnet kopplat till nodens egen minneskontroller. Noderna är förbundna med snabba länkar som Intels UPI eller AMD:s Infinity Fabric.

En kärna når sitt lokala minne direkt och snabbt. Behöver den minne som tillhör en annan nod måste begäran färdas över nodlänken, vilket kostar extra tid: mätningar visar typiskt 50–60 procent högre minneslatens vid fjärråtkomst, ibland uppåt det dubbla, samtidigt som bandbredden kan halveras. Om en VMs vCPUs kör på nod 0 men minnet råkat allokeras på nod 1 betalar varje minnesåtkomst en onödig NUMA-skatt.

Det gäller inte bara dubbelsocklade servrar: även en ensocklad Ryzen med flera CCD:er (Core Complex Dies) beter sig NUMA-likt, eftersom åtkomst över Infinity Fabric är dyrare än inom chipleten. Nästan varje kraftfull hemmaserver är alltså ett NUMA-system i någon form.

Varför core pinning av vCPUs spelar roll

Utan pinning flyttar Linux schedulern varje vCPU fritt mellan fysiska kärnor. Varje migration har ett pris: L1- och L2-cachen töms i praktiken, och tråden kan landa på en kärna i en annan NUMA-nod än där VM:ens minne finns. Resultatet blir varierande latens och sämre cacheträffar – särskilt märkbart i jitterkänsliga arbetslaster.

Med core pinning – i libvirt-termer vcpupin med ett cpuset – låser du varje vCPU till en fast fysisk kärna: tråden migrerar aldrig, cachen hålls varm och minneslokaliteten blir stabil. Tänk på SMT: två logiska trådar som delar en fysisk kärna delar också exekveringsresurser, så pinna en vCPU per fysisk kärna och undvik syskontrådarna för prestanda-VM:er.

Emulator pinning: glöm inte QEMU-trådarna

QEMU kör också emulator-trådar som hanterar I/O, timers och enhetsemulering – och som standard får de köras var som helst, inklusive på de kärnor du pinnat åt vCPU:erna. Då stjäl de cykler mitt i gästens beräkningar. Lösningen är emulatorpin: en post i libvirt-XML:ens cputune-block som pekar emulator-trådarna mot värdens egna kärnor, till exempel cpuset=”14,15″. Reservera en eller två kärnor åt värdsystemet och emulator-trådarna och pinna resten till VM:ens vCPUs.

isolcpus och nohz_full: ge VM:en ostörd tid

Kernelparametern isolcpus=4-11 tar de angivna kärnorna ur schedulerns vanliga pool – endast explicit pinnade trådar får köra där. Komplettera med nohz_full för att minska timeravbrotten på de isolerade kärnorna och rcu_nocbs för att flytta RCU-callbacks därifrån. Resultatet blir minimal jitter: inga värdprocesser och inga avbrott.

Baksidan är att isolerade kärnor är låsta till sin VM – står den sysslolös står kärnorna också sysslolösa. Använd därför isolcpus endast för dedikerade prestanda-VM:er där determinism är värd priset, som en spel-VM med GPU-passthrough eller en databas med hårda latenskrav.

Kontrollera topologin: lscpu och numactl

Det vanligaste misstaget är att pinna på måfå. Kärnnumreringen följer inte alltid den fysiska layouten – särskilt inte på chiplet-CPU:er – och en felaktig gissning kan placera vCPU:erna utspridda över två CCD:er, vilket ger sämre latens än ingen pinning alls. Kartlägg därför alltid topologin först:

lscpu | grep NUMA
lscpu --extended
numactl --hardware
lstopo            # från paketet hwloc, ritar topologin
cat /proc/cpuinfo # visar core id och syskontrådar
numastat          # minnesfördelning per NUMA-nod

numactl –hardware visar varje nods kärnor, minnesstorlek och avståndet mellan noderna – avståndssiffran visar hur mycket dyrare fjärråtkomst är på just din maskin.

Praktiska exempel: NUMA och pinning i Proxmox

I Proxmox VE ställer du in NUMA och CPU-affinitet direkt i VM:ens konfigurationsfil, till exempel /etc/pve/qemu-server/100.conf. Ett typiskt upplägg för en prestanda-VM inom NUMA-nod 0:

cores: 8
cpu: host
affinity: 0-7
numa: 1
numa0: cpus=0-7,hostnodes=0,memory=16384,policy=bind

Här binder affinity VM:ens processer till kärnorna 0–7, numa: 1 aktiverar NUMA i gästen, och numa0-posten mappar gästens nod till värdens nod 0 med 16 GB lokalt allokerat minne via policy=bind. Välj CPU-typen host. Håll vCPU-antalet inom en nods kärnor – en VM som spänner över två noder får oundvikligen fjärråtkomst oavsett pinning.

Exempel: pinning-layout på en värd med 16 kärnor i två NUMA-noder

ArbetslastvCPUsPinnade fysiska kärnorNUMA-nod
Spel-VM med GPU-passthrough80–70
Databas-VM68–131
Emulator-trådar och värdsystem–14–151
Övriga VM:er (ingen pinning)–delas via schedulernbåda

När lönar det sig – och när räcker schedulern?

För en filserver, en mediaserver eller labb-VM:er med lätt last tillför manuell pinning sällan något mätbart, och felaktig pinning kan skada genom överprenumeration eller olycklig placering över chiplet-gränser.

Pinning lönar sig när latens och förutsägbarhet är kritiska: spel-VM:er med GPU-passthrough där ojämna frametimes märks direkt, databaser med tung last samt ljudbearbetning och VoIP. Tumregeln är enkel: mät först, pinna sedan, och mät igen – pinning utan mätning är bara en gissning.

SituationRekommendation
Spel-VM med GPU-passthroughPinna, isolera med isolcpus och använd emulatorpin
Databas med tung transaktionslastPinna vCPUs inom en NUMA-nod, bind minnet lokalt
Ljudbearbetning och realtidsarbetslasterPinna och isolera för minimal jitter
Filserver, mediaserver, backupLåt schedulern sköta det
Labb- och test-VM:erLåt schedulern sköta det
Få kärnor och många samtidiga VM:erPinna inte – risk för överprenumeration

Slutsats: praktiska köpråd

Core pinning och NUMA-medvetenhet är inga mirakelkurer, men för rätt arbetslast är de skillnaden mellan ryckig och silkeslen prestanda. Kartlägg alltid topologin med lscpu och numactl –hardware, håll varje prestanda-VM inom en NUMA-nod med minnet bundet lokalt via policy=bind, och reservera kärnor åt värden och emulator-trådarna. Spara isolcpus och nohz_full till VM:er där determinism motiverar låsta kärnor – och verifiera alltid effekten med mätningar före och efter.

Ska du bygga eller uppgradera: välj en CPU med många kärnor per NUMA-nod eller CCD så att dina största VM:er ryms inom en nod, fördela minnet jämnt över kanaler och noder, och placera PCIe-enheter som GPU:er för passthrough på samma nods lanes som de kärnor du pinnar. Med hårdvara dimensionerad för lokalitet blir optimeringen enklare – och övriga VM:er kan du tryggt lämna åt schedulern.

Leave a Comment