NUMA-Architektur
Das herkömmliche Modell für die Multiprozessorarchitektur ist symmetrischer Multiprozessor (SMP). In diesem Modell verfügt jeder Prozessor über gleichen Zugriff auf Arbeitsspeicher und E/A. Wenn weitere Prozessoren hinzugefügt werden, wird der Prozessorbus zu einer Einschränkung für die Systemleistung.
Systemdesigner verwenden nicht einheitliche Speicherzugriffe (NUMA), um die Prozessorgeschwindigkeit zu erhöhen, ohne die Auslastung des Prozessorbus zu erhöhen. Die Architektur ist nicht einheitlich, da jeder Prozessor nahe an einigen Teilen des Arbeitsspeichers liegt und weiter von anderen Teilen des Arbeitsspeichers entfernt ist. Der Prozessor erhält schnell Zugriff auf den Speicher, dem es nahe ist, während es länger dauern kann, um zugriff auf den Speicher zu erhalten, der weiter entfernt ist.
In einem NUMA-System werden CPUs in kleineren Systemen angeordnet, die als Knotenbezeichnet werden. Jeder Knoten verfügt über eigene Prozessoren und Arbeitsspeicher und ist über einen cacheeinheitlichen Verbindungsbus mit dem größeren System verbunden.
Das System versucht, die Leistung zu verbessern, indem Threads auf Prozessoren geplant werden, die sich im selben Knoten wie der verwendete Arbeitsspeicher befinden. Es versucht, Speicherzuweisungsanforderungen innerhalb des Knotens zu erfüllen, weist jedoch bei Bedarf Speicher von anderen Knoten zu. Außerdem stellt sie eine API bereit, um die Topologie des Systems für Anwendungen verfügbar zu machen. Sie können die Leistung Ihrer Anwendungen verbessern, indem Sie die NUMA-Funktionen verwenden, um die Planung und Speicherauslastung zu optimieren.
Zunächst müssen Sie das Layout von Knoten im System bestimmen. Um den höchsten nummerierten Knoten im System abzurufen, verwenden Sie die GetNumaHighestNodeNumber Funktion. Beachten Sie, dass diese Zahl nicht garantiert der Gesamtanzahl der Knoten im System entspricht. Außerdem werden Knoten mit sequenziellen Zahlen nicht garantiert eng miteinander verbunden. Um die Liste der Prozessoren auf dem System abzurufen, verwenden Sie die GetProcessAffinityMask--Funktion. Sie können den Knoten für jeden Prozessor in der Liste ermitteln, indem Sie die GetNumaProcessorNode--Funktion verwenden. Verwenden Sie alternativ die GetNumaNodeProcessorMask--Funktion, um eine Liste aller Prozessoren in einem Knoten abzurufen.
Nachdem Sie ermittelt haben, welche Prozessoren zu welchen Knoten gehören, können Sie die Leistung Ihrer Anwendung optimieren. Um sicherzustellen, dass alle Threads für Ihren Prozess auf demselben Knoten ausgeführt werden, verwenden Sie die SetProcessAffinityMask--Funktion mit einer Prozessaffinitätsmaske, die Prozessoren im selben Knoten angibt. Dies erhöht die Effizienz von Anwendungen, deren Threads auf denselben Speicher zugreifen müssen. Verwenden Sie alternativ die SetThreadAffinityMask--Funktion, um die Anzahl der Threads auf jedem Knoten zu beschränken.
Speicherintensive Anwendungen müssen ihre Speicherauslastung optimieren. Verwenden Sie die GetNumaAvailableMemoryNode-Funktion, um den freien Arbeitsspeicher abzurufen, der für einen Knoten verfügbar ist. Mit der VirtualAllocExNuma--Funktion kann die Anwendung einen bevorzugten Knoten für die Speicherzuweisung angeben. VirtualAllocExNuma keine physischen Seiten zuordnet, sodass es erfolgreich ist, ob die Seiten auf diesem Knoten oder an anderer Stelle im System verfügbar sind. Die physischen Seiten werden bei Bedarf zugewiesen. Wenn der bevorzugte Knoten nicht mehr vorhanden ist, verwendet der Speicher-Manager Seiten von anderen Knoten. Wenn der Speicher ausgelagert wird, wird derselbe Prozess verwendet, wenn er wieder eingerückt wird.
NUMA-Unterstützung auf Systemen mit mehr als 64 logischen Prozessoren
Auf Systemen mit mehr als 64 logischen Prozessoren werden Knoten Prozessorgruppen entsprechend der Kapazität der Knoten zugewiesen. Die Kapazität eines Knotens ist die Anzahl der Prozessoren, die vorhanden sind, wenn das System mit allen zusätzlichen logischen Prozessoren beginnt, die hinzugefügt werden können, während das System ausgeführt wird.
Windows Server 2008, Windows Vista, Windows Server 2003 und Windows XP: Prozessorgruppen werden nicht unterstützt.
Jeder Knoten muss vollständig in einer Gruppe enthalten sein. Wenn die Kapazitäten der Knoten relativ klein sind, weist das System der gleichen Gruppe mehrere Knoten zu, wobei Knoten ausgewählt werden, die physisch nahe beieinander liegen, um eine bessere Leistung zu erzielen. Wenn die Kapazität eines Knotens die maximale Anzahl von Prozessoren in einer Gruppe überschreitet, teilt das System den Knoten in mehrere kleinere Knoten auf, die jeweils klein genug sind, um in eine Gruppe einzupassen.
Ein idealer NUMA-Knoten für einen neuen Prozess kann mithilfe des PROC_THREAD_ATTRIBUTE_PREFERRED_NODE erweiterten Attributs angefordert werden, wenn der Prozess erstellt wird. Wie bei einem idealen Threadprozessor ist der ideale Knoten ein Hinweis auf den Scheduler, der den neuen Prozess der Gruppe zuweist, die den angeforderten Knoten enthält, falls möglich.
Die erweiterten NUMA-Funktionen GetNumaAvailableMemoryNodeEx, GetNumaNodeProcessorMaskEx, GetNumaProcessorNodeExund GetNumaProximityNodeEx unterscheiden sich von ihren unbeaufsichtigten Entsprechungen darin, dass die Knotennummer ein USHORT--Wert und nicht ein UCHAR-ist, um die potenziell größere Anzahl von Knoten auf einem System mit mehr als 64 logischen Prozessoren aufzunehmen. Außerdem umfasst der mit oder abgerufene Prozessor durch die erweiterten Funktionen die Prozessorgruppe; Der mit oder von den unbeaufsichtigten Funktionen angegebene Prozessor ist gruppenrelativ. Ausführliche Informationen finden Sie in den Referenzthemen zu den einzelnen Funktionen.
Eine gruppenfähige Anwendung kann alle Threads einem bestimmten Knoten ähnlich wie in diesem Thema beschrieben zuweisen, indem sie die entsprechenden erweiterten NUMA-Funktionen verwenden. Die Anwendung verwendet GetLogicalProcessorInformationEx-, um die Liste aller Prozessoren auf dem System abzurufen. Beachten Sie, dass die Anwendung die Prozessaffinitätsmaske nicht festlegen kann, es sei denn, der Prozess wird einer einzelnen Gruppe zugewiesen, und der beabsichtigte Knoten befindet sich in dieser Gruppe. In der Regel muss die Anwendung SetThreadGroupAffinity- aufrufen, um die Threads auf den vorgesehenen Knoten zu beschränken.
Verhalten ab Windows 10 Build 20348
Anmerkung
Ab Windows 10 Build 20348 wurde das Verhalten dieser und anderer NUMA-Funktionen geändert, um Systeme mit Knoten mit mehr als 64 Prozessoren besser zu unterstützen.
Das Erstellen von "gefälschten" Knoten zur Aufnahme einer 1:1-Zuordnung zwischen Gruppen und Knoten hat zu verwirrenden Verhaltensweisen geführt, bei denen unerwartete Anzahl von NUMA-Knoten gemeldet wird. Daher hat sich das Betriebssystem ab Windows 10 Build 20348 geändert, damit mehrere Gruppen einem Knoten zugeordnet werden können, und jetzt kann die wahre NUMA-Topologie des Systems gemeldet werden.
Im Rahmen dieser Änderungen am Betriebssystem wurde eine Reihe von NUMA-APIs geändert, um die Berichterstellung mehrerer Gruppen zu unterstützen, die jetzt einem einzelnen NUMA-Knoten zugeordnet werden können. Aktualisierte und neue APIs werden in der Tabelle im abschnitt NUMA-API unten bezeichnet.
Da sich das Entfernen der Knotenaufteilung auf vorhandene Anwendungen auswirken kann, steht ein Registrierungswert zur Verfügung, um das Zurückmelden des älteren Knotenaufteilungsverhaltens zu ermöglichen. Die Knotenteilung kann erneut aktiviert werden, indem ein REG_DWORD Wert namens "SplitLargeNodes" mit dem Wert 1 unter HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\NUMAerstellt wird. Änderungen an dieser Einstellung erfordern einen Neustart, der wirksam wird.
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\NUMA" /v SplitLargeNodes /t REG_DWORD /d 1
Anmerkung
Anwendungen, die aktualisiert werden, um die neue API-Funktionalität zu verwenden, die die wahre NUMA-Topologie meldet, funktionieren weiterhin ordnungsgemäß auf Systemen, auf denen große Knotenteilung mit diesem Registrierungsschlüssel erneut aktiviert wurde.
Im folgenden Beispiel werden zunächst potenzielle Probleme mit Builds-Tabellen veranschaulicht, die Prozessoren mithilfe der älteren Affinitäts-APIs zuordnen, die keine vollständige Abdeckung aller Prozessoren im System mehr bieten, was zu einer unvollständigen Tabelle führen kann. Die Auswirkungen einer solchen Unvollständigkeit hängen vom Inhalt der Tabelle ab. Wenn die Tabelle einfach die entsprechende Knotennummer speichert, ist dies wahrscheinlich nur ein Leistungsproblem mit aufgedeckten Prozessoren, die als Teil von Knoten 0 verbleiben. Wenn die Tabelle jedoch Zeiger auf eine Kontextstruktur pro Knoten enthält, kann dies zur Laufzeit zu NULL-Ableitungen führen.
Als Nächstes veranschaulicht das Codebeispiel zwei Problemumgehungen für das Problem. Die erste besteht darin, zu den Affinitäts-APIs für mehrere Gruppen (Benutzermodus und Kernelmodus) zu migrieren. Die zweite besteht darin, KeQueryLogicalProcessorRelationship zu verwenden, um den NUMA-Knoten, der einer bestimmten Prozessornummer zugeordnet ist, direkt abzufragen.
//
// 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 der folgenden Tabelle wird die NUMA-API beschrieben.
Funktion | Beschreibung |
---|---|
AllocateUserPhysicalPagesNuma | Weist physische Speicherseiten zu, die in einem beliebigen Adressfenstererweiterungen (AWE)-Bereich eines angegebenen Prozesses zugeordnet und nicht zugeordnet werden sollen, und gibt den NUMA-Knoten für den physischen Speicher an. |
CreateFileMappingNuma | Erstellt oder öffnet ein benanntes oder unbenannte Dateizuordnungsobjekt für eine angegebene Datei und gibt den NUMA-Knoten für den physischen Speicher an. |
GetLogicalProcessorInformation | Aktualisiert in Windows 10 Build 20348. Ruft Informationen zu logischen Prozessoren und zugehöriger Hardware ab. |
GetLogicalProcessorInformationEx- | Aktualisiert in Windows 10 Build 20348. Ruft Informationen zu den Beziehungen von logischen Prozessoren und verwandter Hardware ab. |
GetNumaAvailableMemoryNode | Ruft die Im angegebenen Knoten verfügbare Arbeitsspeichermenge ab. |
GetNumaAvailableMemoryNodeEx | Ruft die Menge an Arbeitsspeicher in einem Knoten ab, der als USHORT--Wert angegeben ist. |
GetNumaHighestNodeNumber | Ruft den Knoten ab, der derzeit die höchste Zahl aufweist. |
GetNumaNodeProcessorMask | Aktualisiert in Windows 10 Build 20348. Ruft das Prozessorformat für den angegebenen Knoten ab. |
GetNumaNodeProcessorMask2 | Neu in Windows 10 Build 20348. Ruft das Prozessorformat für mehrere Gruppen des angegebenen Knotens ab. |
GetNumaNodeProcessorMaskEx | Aktualisiert in Windows 10 Build 20348. Ruft das Prozessorformat für einen Knoten ab, der als USHORT--Wert angegeben ist. |
GetNumaProcessorNode | Ruft die Knotennummer für den angegebenen Prozessor ab. |
GetNumaProcessorNodeEx | Ruft die Knotennummer als USHORT- Wert für den angegebenen Prozessor ab. |
GetNumaProximityNode | Ruft die Knotennummer für den angegebenen Näherungsbezeichner ab. |
GetNumaProximityNodeEx | Ruft die Knotennummer als USHORT- Wert für den angegebenen Näherungsbezeichner ab. |
GetProcessDefaultCpuSetMasks | Neu in Windows 10 Build 20348. Ruft die Liste der CPU-Sätze im Prozessstandardsatz ab, der von SetProcessDefaultCpuSetMasks oder SetProcessDefaultCpuSets festgelegt wurde. |
GetThreadSelectedCpuSetMasks | Neu in Windows 10 Build 20348. Legt die ausgewählte CPU Sets-Zuordnung für den angegebenen Thread fest. Diese Zuweisung setzt die Standardzuweisung des Prozesses außer Kraft, wenn eine festgelegt ist. |
MapViewOfFileExNuma | Ordnet eine Ansicht einer Dateizuordnung dem Adressraum eines Aufrufvorgangs zu und gibt den NUMA-Knoten für den physischen Speicher an. |
SetProcessDefaultCpuSetMasks | Neu in Windows 10 Build 20348. Legt die Standard-CPU-Sätze-Zuordnung für Threads im angegebenen Prozess fest. |
SetThreadSelectedCpuSetMasks | Neu in Windows 10 Build 20348. Legt die ausgewählte CPU Sets-Zuordnung für den angegebenen Thread fest. Diese Zuweisung setzt die Standardzuweisung des Prozesses außer Kraft, wenn eine festgelegt ist. |
VirtualAllocExNuma | Reserviert oder committ einen Bereich des Arbeitsspeichers innerhalb des virtuellen Adressraums des angegebenen Prozesses und gibt den NUMA-Knoten für den physischen Speicher an. |
Die QueryWorkingSetEx--Funktion kann verwendet werden, um den NUMA-Knoten abzurufen, auf dem eine Seite zugeordnet ist. Ein Beispiel finden Sie unter Allocating Memory from a NUMA Node.
Verwandte Themen