Dela via


NUMA-arkitektur

Den traditionella modellen för arkitektur med flera processorer är symmetrisk multiprocessor (SMP). I den här modellen har varje processor samma åtkomst till minne och I/O. När fler processorer läggs till blir processorbussen en begränsning för systemets prestanda.

Systemdesigners använder icke-enhetlig minnesåtkomst (NUMA) för att öka processorhastigheten utan att öka belastningen på processorbussen. Arkitekturen är inte enhetlig eftersom varje processor ligger nära vissa delar av minnet och längre från andra delar av minnet. Processorn får snabbt åtkomst till det minne som den är nära, medan det kan ta längre tid att få åtkomst till minne som ligger längre bort.

I ett NUMA-system ordnas processorer i mindre system som kallas noder. Varje nod har sina egna processorer och minne och är ansluten till det större systemet via en cachesammanhållen sammanlänkningsbuss.

Systemet försöker förbättra prestanda genom att schemalägga trådar på processorer som finns i samma nod som det minne som används. Den försöker uppfylla minnesallokeringsbegäranden från noden, men allokerar minne från andra noder om det behövs. Det tillhandahåller också ett API för att göra systemets topologi tillgänglig för program. Du kan förbättra prestandan för dina program med hjälp av NUMA-funktionerna för att optimera schemaläggning och minnesanvändning.

Först och främst måste du bestämma layouten för noder i systemet. Om du vill hämta den högsta numrerade noden i systemet använder du funktionen GetNumaHighestNodeNumber. Observera att det här antalet inte garanteras vara lika med det totala antalet noder i systemet. Noder med sekventiella tal garanteras inte heller vara nära varandra. Om du vill hämta listan över processorer i systemet använder du funktionen GetProcessAffinityMask. Du kan fastställa noden för varje processor i listan med hjälp av funktionen GetNumaProcessorNode. Du kan också hämta en lista över alla processorer i en nod genom att använda funktionen GetNumaNodeProcessorMask.

När du har fastställt vilka processorer som tillhör vilka noder kan du optimera programmets prestanda. För att säkerställa att alla trådar för processen körs på samma nod använder du funktionen SetProcessAffinityMask med en processtillhörighetsmask som anger processorer i samma nod. Detta ökar effektiviteten för program vars trådar behöver komma åt samma minne. Om du vill begränsa antalet trådar på varje nod kan du också använda funktionen SetThreadAffinityMask.

Minnesintensiva program måste optimera minnesanvändningen. Om du vill hämta mängden ledigt minne som är tillgängligt för en nod använder du funktionen GetNumaAvailableMemoryNode. Funktionen VirtualAllocExNuma gör att programmet kan ange en önskad nod för minnesallokeringen. VirtualAllocExNuma allokerar inga fysiska sidor, så det lyckas oavsett om sidorna är tillgängliga på noden eller någon annanstans i systemet. De fysiska sidorna allokeras på begäran. Om den önskade noden får slut på sidor använder minneshanteraren sidor från andra noder. Om minnet är utsålt används samma process när det tas in igen.

NUMA-stöd för system med fler än 64 logiska processorer

På system med fler än 64 logiska processorer tilldelas noder till processorgrupper beroende på nodernas kapacitet. Kapaciteten för en nod är antalet processorer som finns när systemet startar tillsammans med eventuella ytterligare logiska processorer som kan läggas till medan systemet körs.

Windows Server 2008, Windows Vista, Windows Server 2003 och Windows XP: Processorgrupper stöds inte.

Varje nod måste vara helt innesluten i en grupp. Om nodernas kapaciteter är relativt små tilldelar systemet mer än en nod till samma grupp och väljer noder som är fysiskt nära varandra för bättre prestanda. Om en nods kapacitet överskrider det maximala antalet processorer i en grupp delar systemet upp noden i flera mindre noder, var och en tillräckligt liten för att få plats i en grupp.

En idealisk NUMA-nod för en ny process kan begäras med hjälp av det PROC_THREAD_ATTRIBUTE_PREFERRED_NODE utökade attributet när processen skapas. Precis som en trådideal processor är den idealiska noden ett tips till schemaläggaren, som tilldelar den nya processen till den grupp som innehåller den begärda noden om möjligt.

De utökade NUMA-funktionerna GetNumaAvailableMemoryNodeEx, GetNumaNodeProcessorMaskEx, GetNumaProcessorNodeExoch GetNumaProximityNodeEx skiljer sig från deras obevakade motsvarigheter eftersom nodnumret är ett USHORT- värde i stället för ett UCHAR-för att hantera det potentiellt större antalet noder i ett system med fler än 64 logiska processorer. Dessutom innehåller den processor som anges med eller hämtas av de utökade funktionerna processorgruppen. processorn som anges med eller hämtas av de obevakade funktionerna är grupprelativ. Mer information finns i referensavsnitten för enskilda funktioner.

Ett gruppmedvetent program kan tilldela alla sina trådar till en viss nod på ett liknande sätt som det som beskrevs tidigare i det här avsnittet med hjälp av motsvarande utökade NUMA-funktioner. Programmet använder GetLogicalProcessorInformationEx för att hämta listan över alla processorer i systemet. Observera att programmet inte kan ange processtillhörighetsmasken såvida inte processen har tilldelats till en enda grupp och den avsedda noden finns i den gruppen. Vanligtvis måste programmet anropa SetThreadGroupAffinity för att begränsa trådarna till den avsedda noden.

Beteende som börjar med Windows 10 Build 20348

Not

Från och med Windows 10 Build 20348 har beteendet för den här och andra NUMA-funktioner ändrats för att bättre stödja system med noder som innehåller mer än 64 processorer.

Att skapa "falska" noder för att hantera en 1:1-mappning mellan grupper och noder har resulterat i förvirrande beteenden där oväntade antal NUMA-noder rapporteras och så, från och med Windows 10 Build 20348, har operativsystemet ändrats så att flera grupper kan associeras med en nod, och därför kan systemets sanna NUMA-topologi rapporteras.

Som en del av dessa ändringar i operativsystemet har ett antal NUMA-API:er ändrats för att stödja rapportering av flera grupper som nu kan associeras med en enda NUMA-nod. Uppdaterade och nya API:er är märkta i tabellen i avsnittet NUMA API nedan.

Eftersom borttagning av noddelning potentiellt kan påverka befintliga program är ett registervärde tillgängligt för att tillåta att du väljer tillbaka till det äldre beteendet för noddelning. Noddelning kan återaktiveras genom att skapa ett REG_DWORD värde med namnet "SplitLargeNodes" med värdet 1 under HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\NUMA. Ändringar i den här inställningen kräver att en omstart börjar gälla.

reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\NUMA" /v SplitLargeNodes /t REG_DWORD /d 1

Not

Program som uppdateras för att använda den nya API-funktionen som rapporterar den sanna NUMA-topologin fortsätter att fungera korrekt på system där stor noddelning har återaktiverats med den här registernyckeln.

I följande exempel visas först potentiella problem med att versionstabeller mappar processorer till NUMA-noder med hjälp av äldre tillhörighets-API:er, vilket inte längre ger en fullständig beskrivning av alla processorer i systemet, vilket kan resultera i en ofullständig tabell. Konsekvenserna av en sådan ofullständighet beror på innehållet i tabellen. Om tabellen helt enkelt lagrar motsvarande nodnummer är detta förmodligen bara ett prestandaproblem där oskyddade processorer lämnas kvar som en del av nod 0. Men om tabellen innehåller pekare till en kontextstruktur per nod kan detta resultera i NULL-avrefereringar vid körning.

Därefter illustrerar kodexemplet två lösningar på problemet. Den första är att migrera till API:erna för nodtillhörighet för flera grupper (användarläge och kernelläge). Den andra är att använda KeQueryLogicalProcessorRelationship för att direkt fråga DEN NUMA-nod som är associerad med ett visst processornummer.


//
// Problematic implementation using KeQueryNodeActiveAffinity.
//

USHORT CurrentNode;
USHORT HighestNodeNumber;
GROUP_AFFINITY NodeAffinity;
ULONG ProcessorIndex;
PROCESSOR_NUMBER ProcessorNumber;

HighestNodeNumber = KeQueryHighestNodeNumber();
for (CurrentNode = 0; CurrentNode <= HighestNodeNumber; CurrentNode += 1) {

    KeQueryNodeActiveAffinity(CurrentNode, &NodeAffinity, NULL);
    while (NodeAffinity.Mask != 0) {

        ProcessorNumber.Group = NodeAffinity.Group;
        BitScanForward(&ProcessorNumber.Number, NodeAffinity.Mask);

        ProcessorIndex = KeGetProcessorIndexFromNumber(&ProcessorNumber);

        ProcessorNodeContexts[ProcessorIndex] = NodeContexts[CurrentNode;]

        NodeAffinity.Mask &= ~((KAFFINITY)1 << ProcessorNumber.Number);
    }
}

//
// Resolution using KeQueryNodeActiveAffinity2.
//

USHORT CurrentIndex;
USHORT CurrentNode;
USHORT CurrentNodeAffinityCount;
USHORT HighestNodeNumber;
ULONG MaximumGroupCount;
PGROUP_AFFINITY NodeAffinityMasks;
ULONG ProcessorIndex;
PROCESSOR_NUMBER ProcessorNumber;
NTSTATUS Status;

MaximumGroupCount = KeQueryMaximumGroupCount();
NodeAffinityMasks = ExAllocatePool2(POOL_FLAG_PAGED,
                                    sizeof(GROUP_AFFINITY) * MaximumGroupCount,
                                    'tseT');

if (NodeAffinityMasks == NULL) {
    return STATUS_NO_MEMORY;
}

HighestNodeNumber = KeQueryHighestNodeNumber();
for (CurrentNode = 0; CurrentNode <= HighestNodeNumber; CurrentNode += 1) {

    Status = KeQueryNodeActiveAffinity2(CurrentNode,
                                        NodeAffinityMasks,
                                        MaximumGroupCount,
                                        &CurrentNodeAffinityCount);
    NT_ASSERT(NT_SUCCESS(Status));

    for (CurrentIndex = 0; CurrentIndex < CurrentNodeAffinityCount; CurrentIndex += 1) {

        CurrentAffinity = &NodeAffinityMasks[CurrentIndex];

        while (CurrentAffinity->Mask != 0) {

            ProcessorNumber.Group = CurrentAffinity.Group;
            BitScanForward(&ProcessorNumber.Number, CurrentAffinity->Mask);

            ProcessorIndex = KeGetProcessorIndexFromNumber(&ProcessorNumber);

            ProcessorNodeContexts[ProcessorIndex] = NodeContexts[CurrentNode];

            CurrentAffinity->Mask &= ~((KAFFINITY)1 << ProcessorNumber.Number);
        }
    }
}

//
// Resolution using KeQueryLogicalProcessorRelationship.
//

ULONG ProcessorCount;
ULONG ProcessorIndex;
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX ProcessorInformation;
ULONG ProcessorInformationSize;
PROCESSOR_NUMBER ProcessorNumber;
NTSTATUS Status;

ProcessorCount = KeQueryActiveProcessorCountEx(ALL_PROCESSOR_GROUPS);

for (ProcessorIndex = 0; ProcessorIndex < ProcessorCount; ProcessorIndex += 1) {

    Status = KeGetProcessorNumberFromIndex(ProcessorIndex, &ProcessorNumber);
    NT_ASSERT(NT_SUCCESS(Status));

    ProcessorInformationSize = sizeof(ProcessorInformation);
    Status = KeQueryLogicalProcessorRelationship(&ProcessorNumber,
                                                    RelationNumaNode,
                                                    &ProcessorInformation,
                                                    &ProcesorInformationSize);
    NT_ASSERT(NT_SUCCESS(Status));

    NodeNumber = ProcessorInformation.NumaNode.NodeNumber;

    ProcessorNodeContexts[ProcessorIndex] = NodeContexts[NodeNumber];
}

NUMA-API

I följande tabell beskrivs NUMA-API:et.

Funktion Beskrivning
AllocateUserPhysicalPagesNuma Allokerar fysiska minnessidor som ska mappas och avmappas inom alla adressfönstertillägg region (AWE) för en angiven process och anger NUMA-noden för det fysiska minnet.
CreateFileMappingNuma Skapar eller öppnar ett namngivet eller namnlöst filmappningsobjekt för en angiven fil och anger NUMA-noden för det fysiska minnet.
GetLogicalProcessorInformation Uppdaterad i Windows 10 Build 20348. Hämtar information om logiska processorer och relaterad maskinvara.
GetLogicalProcessorInformationEx Uppdaterad i Windows 10 Build 20348. Hämtar information om relationerna mellan logiska processorer och relaterad maskinvara.
GetNumaAvailableMemoryNode Hämtar mängden minne som är tillgängligt i den angivna noden.
GetNumaAvailableMemoryNodeEx Hämtar mängden minne som är tillgängligt i en nod som anges som ett USHORT- värde.
GetNumaHighestNodeNumber Hämtar den nod som för närvarande har det högsta talet.
GetNumaNodeProcessorMask Uppdaterad i Windows 10 Build 20348. Hämtar processormasken för den angivna noden.
GetNumaNodeProcessorMask2 Nytt i Windows 10 Build 20348. Hämtar processormasken för flera grupper för den angivna noden.
GetNumaNodeProcessorMaskEx Uppdaterad i Windows 10 Build 20348. Hämtar processormasken för en nod som anges som ett USHORT- värde.
GetNumaProcessorNode Hämtar nodnumret för den angivna processorn.
GetNumaProcessorNodeEx Hämtar nodnumret som ett USHORT- värde för den angivna processorn.
GetNumaProximityNode Hämtar nodnumret för den angivna närhetsidentifieraren.
GetNumaProximityNodeEx Hämtar nodnumret som ett USHORT- värde för den angivna närhetsidentifieraren.
GetProcessDefaultCpuSetMasks Nytt i Windows 10 Build 20348. Hämtar listan över CPU-uppsättningar i den processstandarduppsättning som angavs av SetProcessDefaultCpuSetMasks eller SetProcessDefaultCpuSets.
GetThreadSelectedCpuSetMasks Nytt i Windows 10 Build 20348. Anger den valda tilldelningen av CPU-uppsättningar för den angivna tråden. Den här tilldelningen åsidosätter processens standardtilldelning, om en har angetts.
MapViewOfFileExNuma Mappar en vy över en filmappning till adressutrymmet för en anropsprocess och anger NUMA-noden för det fysiska minnet.
SetProcessDefaultCpuSetMasks Nytt i Windows 10 Build 20348. Anger standardtilldelningen för CPU-uppsättningar för trådar i den angivna processen.
SetThreadSelectedCpuSetMasks Nytt i Windows 10 Build 20348. Anger den valda tilldelningen av CPU-uppsättningar för den angivna tråden. Den här tilldelningen åsidosätter processens standardtilldelning, om en har angetts.
VirtualAllocExNuma Reserverar eller checkar in en minnesregion inom det virtuella adressutrymmet för den angivna processen och anger NUMA-noden för det fysiska minnet.

 

Funktionen QueryWorkingSetEx kan användas för att hämta NUMA-noden där en sida allokeras. Ett exempel finns i Allokera minne från en NUMA-nod.

allokera minne från en NUMA-nod

flera processorer

processorgrupper