Delen via


NUMA-architectuur

Het traditionele model voor multiprocessorarchitectuur is symmetrische multiprocessor (SMP). In dit model heeft elke processor gelijke toegang tot geheugen en I/O. Naarmate er meer processors worden toegevoegd, wordt de processorbus een beperking voor systeemprestaties.

Systeemontwerpers gebruiken niet-uniforme geheugentoegang (NUMA) om de processorsnelheid te verhogen zonder de belasting van de processorbus te verhogen. De architectuur is niet uniform omdat elke processor zich dicht bij sommige delen van het geheugen bevindt en verder van andere delen van het geheugen. De processor krijgt snel toegang tot het geheugen dat het dicht bij zich heeft, terwijl het langer kan duren om toegang te krijgen tot het geheugen dat verder weg is.

In een NUMA-systeem worden CPU's gerangschikt in kleinere systemen die knooppuntenworden genoemd. Elk knooppunt heeft zijn eigen processors en geheugen en is verbonden met het grotere systeem via een cache-coherente interconnect-bus.

Het systeem probeert de prestaties te verbeteren door threads te plannen op processors die zich in hetzelfde knooppunt bevinden als het geheugen dat wordt gebruikt. Het probeert te voldoen aan aanvragen voor geheugentoewijzing vanuit het knooppunt, maar wijst indien nodig geheugen van andere knooppunten toe. Het biedt ook een API om de topologie van het systeem beschikbaar te maken voor toepassingen. U kunt de prestaties van uw toepassingen verbeteren met behulp van de NUMA-functies om het plannings- en geheugengebruik te optimaliseren.

Allereerst moet u de indeling van knooppunten in het systeem bepalen. Als u het hoogste genummerde knooppunt in het systeem wilt ophalen, gebruikt u de functie GetNumaHighestNodeNumber. Houd er rekening mee dat dit aantal niet gegarandeerd gelijk is aan het totale aantal knooppunten in het systeem. Bovendien zijn knooppunten met opeenvolgende getallen niet gegarandeerd dicht bij elkaar. Als u de lijst met processors op het systeem wilt ophalen, gebruikt u de functie GetProcessAffinityMask. U kunt het knooppunt voor elke processor in de lijst bepalen met behulp van de functie GetNumaProcessorNode. Als u ook een lijst met alle processors in een knooppunt wilt ophalen, gebruikt u de functie GetNumaNodeProcessorMask.

Nadat u hebt vastgesteld welke processors bij welke knooppunten horen, kunt u de prestaties van uw toepassing optimaliseren. Gebruik de functie SetProcessAffinityMask met een procesaffiniteitsmasker dat processors in hetzelfde knooppunt opgeeft om ervoor te zorgen dat alle threads voor uw proces worden uitgevoerd op hetzelfde knooppunt. Dit verhoogt de efficiëntie van toepassingen waarvan threads toegang nodig hebben tot hetzelfde geheugen. Als u het aantal threads op elk knooppunt wilt beperken, gebruikt u de functie SetThreadAffinityMask.

Geheugenintensieve toepassingen moeten hun geheugengebruik optimaliseren. Gebruik de functie GetNumaAvailableMemoryNode om de hoeveelheid vrij geheugen op te halen die beschikbaar is voor een knooppunt. Met de functie VirtualAllocExNuma kan de toepassing een voorkeursknooppunt opgeven voor de geheugentoewijzing. VirtualAllocExNuma- geen fysieke pagina's toewijst, zodat deze slaagt of de pagina's wel of niet beschikbaar zijn op dat knooppunt of elders in het systeem. De fysieke pagina's worden op aanvraag toegewezen. Als het voorkeursknooppunt onvoldoende pagina's bevat, gebruikt de geheugenbeheerder pagina's van andere knooppunten. Als het geheugen wordt uitgepaginad, wordt hetzelfde proces gebruikt wanneer het wordt teruggezet.

NUMA-ondersteuning op systemen met meer dan 64 logische processors

Op systemen met meer dan 64 logische processors worden knooppunten toegewezen aan processorgroepen op basis van de capaciteit van de knooppunten. De capaciteit van een knooppunt is het aantal processors dat aanwezig is wanneer het systeem begint samen met eventuele extra logische processors die kunnen worden toegevoegd terwijl het systeem wordt uitgevoerd.

Windows Server 2008, Windows Vista, Windows Server 2003 en Windows XP: Processor-groepen worden niet ondersteund.

Elk knooppunt moet volledig zijn opgenomen in een groep. Als de capaciteiten van de knooppunten relatief klein zijn, wijst het systeem meer dan één knooppunt toe aan dezelfde groep, waarbij knooppunten worden gekozen die fysiek dicht bij elkaar liggen voor betere prestaties. Als de capaciteit van een knooppunt het maximum aantal processors in een groep overschrijdt, splitst het systeem het knooppunt op in meerdere kleinere knooppunten, elk klein genoeg om in een groep te passen.

Een ideaal NUMA-knooppunt voor een nieuw proces kan worden aangevraagd met behulp van het PROC_THREAD_ATTRIBUTE_PREFERRED_NODE uitgebreide kenmerk wanneer het proces wordt gemaakt. Net als een ideale threadprocessor is het ideale knooppunt een hint voor de planner, die het nieuwe proces indien mogelijk toewijst aan de groep die het aangevraagde knooppunt bevat.

De uitgebreide NUMA-functies GetNumaAvailableMemoryNodeEx, GetNumaNodeProcessorMaskEx, GetNumaProcessorNodeExen GetNumaProximityNodeEx verschilt van de niet-aanvolgende tegenhangers omdat het knooppuntnummer een USHORT- waarde is in plaats van een UCHAR-, om het potentieel grotere aantal knooppunten op een systeem met meer dan 64 logische processors aan te bieden. De processor die is opgegeven met of opgehaald door de uitgebreide functies bevat ook de processorgroep; de processor die is opgegeven met of opgehaald door de unextended-functies, is groeps-relatief. Zie de naslagonderwerpen voor afzonderlijke functies voor meer informatie.

Een groepbewuste toepassing kan alle threads toewijzen aan een bepaald knooppunt op een vergelijkbare manier als die eerder in dit onderwerp is beschreven, met behulp van de bijbehorende uitgebreide NUMA-functies. De toepassing gebruikt GetLogicalProcessorInformationEx om de lijst met alle processors op het systeem op te halen. Houd er rekening mee dat de toepassing het procesaffiniteitsmasker niet kan instellen, tenzij het proces is toegewezen aan één groep en het beoogde knooppunt zich in die groep bevindt. Meestal moet de toepassing SetThreadGroupAffinity aanroepen om de threads te beperken tot het beoogde knooppunt.

Gedrag vanaf Windows 10 Build 20348

Notitie

Vanaf Windows 10 Build 20348 is het gedrag van deze en andere NUMA-functies gewijzigd om systemen beter te ondersteunen met knooppunten met meer dan 64 processors.

Het maken van 'nep'-knooppunten voor een toewijzing van 1:1 tussen groepen en knooppunten heeft geleid tot verwarrend gedrag waarbij onverwachte aantallen NUMA-knooppunten worden gerapporteerd en dus, vanaf Windows 10 Build 20348, het besturingssysteem is gewijzigd zodat meerdere groepen aan een knooppunt kunnen worden gekoppeld, en nu kan de echte NUMA-topologie van het systeem worden gerapporteerd.

Als onderdeel van deze wijzigingen in het besturingssysteem zijn een aantal NUMA-API's gewijzigd ter ondersteuning van het rapporteren van meerdere groepen die nu kunnen worden gekoppeld aan één NUMA-knooppunt. Bijgewerkte en nieuwe API's worden gelabeld in de tabel in de sectie NUMA-API hieronder.

Omdat het verwijderen van het splitsen van knooppunten mogelijk van invloed kan zijn op bestaande toepassingen, is er een registerwaarde beschikbaar waarmee u kunt terugkeren naar het verouderde knooppuntsplitsingsgedrag. Het splitsen van knooppunten kan opnieuw worden ingeschakeld door een REG_DWORD waarde met de naam SplitLargeNodes te maken met de waarde 1 onder HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\NUMA. Voor wijzigingen in deze instelling moet opnieuw worden opgestart.

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

Notitie

Toepassingen die worden bijgewerkt voor het gebruik van de nieuwe API-functionaliteit die rapporteert dat de echte NUMA-topologie correct werkt op systemen waarbij het splitsen van grote knooppunten opnieuw is ingeschakeld met deze registersleutel.

In het volgende voorbeeld ziet u eerst mogelijke problemen met het bouwen van tabellen die processors toewijzen aan NUMA-knooppunten met behulp van de verouderde affiniteits-API's, die niet langer een volledige dekking bieden van alle processors in het systeem, wat kan leiden tot een onvolledige tabel. De gevolgen van deze volledigheid zijn afhankelijk van de inhoud van de tabel. Als in de tabel alleen het bijbehorende knooppuntnummer wordt opgeslagen, is dit waarschijnlijk alleen een prestatieprobleem waarbij onbedekte processors worden achtergelaten als onderdeel van knooppunt 0. Als de tabel echter verwijzingen naar een contextstructuur per knooppunt bevat, kan dit leiden tot NULL-deducties tijdens runtime.

Vervolgens illustreert het codevoorbeeld twee tijdelijke oplossingen voor het probleem. De eerste is om te migreren naar de affiniteits-API's voor meerdere groepen (gebruikersmodus en kernelmodus). De tweede is het gebruik van KeQueryLogicalProcessorRelationship om rechtstreeks een query uit te voeren op het NUMA-knooppunt dat is gekoppeld aan een bepaald 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

In de volgende tabel wordt de NUMA-API beschreven.

Functie Beschrijving
AllocateUserPhysicalPagesNuma- Hiermee wijst u fysieke geheugenpagina's toe die moeten worden toegewezen en niet worden toegewezen binnen een AWE-regio (Address Windowing Extensions) van een opgegeven proces en geeft u het NUMA-knooppunt voor het fysieke geheugen op.
CreateFileMappingNuma- Hiermee maakt of opent u een benoemd of niet-benoemd bestandstoewijzingsobject voor een opgegeven bestand en geeft u het NUMA-knooppunt voor het fysieke geheugen op.
GetLogicalProcessorInformation- Bijgewerkt in Windows 10 Build 20348. Haalt informatie op over logische processors en gerelateerde hardware.
GetLogicalProcessorInformationEx- Bijgewerkt in Windows 10 Build 20348. Haalt informatie op over de relaties van logische processors en gerelateerde hardware.
GetNumaAvailableMemoryNode Hiermee haalt u de hoeveelheid geheugen op die beschikbaar is in het opgegeven knooppunt.
GetNumaAvailableMemoryNodeEx Haalt de hoeveelheid geheugen op die beschikbaar is in een knooppunt dat is opgegeven als een USHORT- waarde.
GetNumaHighestNodeNumber Haalt het knooppunt op dat momenteel het hoogste getal heeft.
GetNumaNodeProcessorMask- Bijgewerkt in Windows 10 Build 20348. Haalt het processormasker voor het opgegeven knooppunt op.
GetNumaNodeProcessorMask2 Nieuw in Windows 10 Build 20348. Haalt het processormasker met meerdere groepen van het opgegeven knooppunt op.
GetNumaNodeProcessorMaskEx- Bijgewerkt in Windows 10 Build 20348. Haalt het processormasker op voor een knooppunt dat is opgegeven als een USHORT- waarde.
GetNumaProcessorNode Haalt het knooppuntnummer voor de opgegeven processor op.
GetNumaProcessorNodeEx- Haalt het knooppuntnummer op als een USHORT- waarde voor de opgegeven processor.
GetNumaProximityNode Hiermee wordt het knooppuntnummer opgehaald voor de opgegeven nabijheids-id.
GetNumaProximityNodeEx Haalt het knooppuntnummer op als een USHORT- waarde voor de opgegeven nabijheids-id.
GetProcessDefaultCpuSetMasks Nieuw in Windows 10 Build 20348. Haalt de lijst met CPU-sets op in de processtandaardset die is ingesteld door SetProcessDefaultCpuSetMasks of SetProcessDefaultCpuSets.
GetThreadSelectedCpuSetMasks- Nieuw in Windows 10 Build 20348. Hiermee stelt u de toewijzing van de geselecteerde CPU-sets voor de opgegeven thread in. Deze toewijzing overschrijft de standaardtoewijzing van het proces, indien ingesteld.
MapViewOfFileExNuma- Wijst een weergave van een bestandstoewijzing toe aan de adresruimte van een aanroepproces en geeft het NUMA-knooppunt voor het fysieke geheugen op.
SetProcessDefaultCpuSetMasks Nieuw in Windows 10 Build 20348. Hiermee stelt u de standaardtoewijzing cpu-sets in voor threads in het opgegeven proces.
SetThreadSelectedCpuSetMasks- Nieuw in Windows 10 Build 20348. Hiermee stelt u de toewijzing van de geselecteerde CPU-sets voor de opgegeven thread in. Deze toewijzing overschrijft de standaardtoewijzing van het proces, indien ingesteld.
VirtualAllocExNuma- Reserveert of voert een geheugengebied in de virtuele adresruimte van het opgegeven proces in en geeft het NUMA-knooppunt voor het fysieke geheugen op.

 

De functie QueryWorkingSetEx kan worden gebruikt om het NUMA-knooppunt op te halen waarop een pagina wordt toegewezen. Zie bijvoorbeeld Geheugen toewijzen vanuit een NUMA-knooppunt.

Geheugen toewijzen vanuit een NUMA-knooppunt

meerdere processors

processorgroepen