Die allgemeine SituationWie kann man reservierten Speicher in C++ bereitstellen?
Eine Anwendung, die extrem intensive sowohl Bandbreite, CPU-Auslastung und GPU Nutzung benötigt etwa 10-15GB pro Sekunde von einer GPU zu einem anderen zu übertragen. Da die DX11-API für den Zugriff auf die GPU verwendet wird, kann das Hochladen auf die GPU nur mit Puffern erfolgen, die für jeden einzelnen Upload eine Zuordnung erfordern. Das Hochladen erfolgt in Blöcken von jeweils 25 MB und 16 Threads schreiben gleichzeitig Puffer in zugeordnete Puffer. Es kann nicht viel getan werden. Die tatsächliche Parallelität der Schreibvorgänge sollte niedriger sein, wenn der folgende Fehler nicht vorhanden wäre.
Es ist eine bullige Workstation mit 3 Pascal GPUs, einem High-End-Haswell-Prozessor und Quad-Channel-RAM. Auf der Hardware kann nicht viel verbessert werden. Es läuft eine Desktop-Version von Windows 10.
Das eigentliche Problem
Sobald ich ~ 50% CPU-Last passieren, etwas in MmPageFault()
(im Windows-Kernel, aufgerufen, wenn Zugriff auf den Speicher, die in Ihrer abgebildet wurde Adressraum, wurde aber vom OS noch nicht übernommen) bricht entsetzlich ab, und die restliche 50% CPU-Last wird bei einem Spin-Lock innerhalb MmPageFault()
verschwendet. Die CPU wird zu 100% ausgelastet und die Anwendungsleistung verschlechtert sich vollständig.
Ich muss davon ausgehen, dass dies aufgrund der immensen Speichermenge ist, die dem Prozess jede Sekunde zugewiesen werden muss und die auch jedes Mal, wenn der DX11-Puffer nicht zugeordnet wird, vollständig aus dem Prozess entfernt wird. Dementsprechend sind es tatsächlich tausende Anrufe nach MmPageFault()
pro Sekunde, die sequentiell als memcpy()
sequentiell in den Puffer schreiben. Für jede einzelne nicht festgelegte Seite.
Einer der CPU-Auslastung geht über 50%, die optimistische Spin-Lock im Windows-Kernel schützt das Seitenmanagement vollständig Leistung beeinträchtigt.
Considerations
Der Puffer von dem DX11 Fahrer zugeordnet ist. An der Allokationsstrategie kann nichts geändert werden. Die Verwendung einer anderen Speicher-API und insbesondere die Wiederverwendung ist nicht möglich.
Aufrufe an die DX11-API (Mapping/Unmapping der Puffer) geschieht alles aus einem einzigen Thread. Die eigentlichen Kopiervorgänge erfolgen möglicherweise multi-threaded über mehrere Threads als es virtuelle Prozessoren im System gibt.
Die Verringerung der Speicherbandbreitenanforderungen ist nicht möglich. Es ist eine Echtzeitanwendung. Tatsächlich ist das harte Limit derzeit die PCIe 3.0 16x-Bandbreite der primären GPU. Wenn ich könnte, müsste ich schon weiter drücken.
Das Vermeiden von Multi-Threading-Kopien ist nicht möglich, da es unabhängige Producer-Consumer-Warteschlangen gibt, die nicht trivial zusammengeführt werden können.
Der Spin-Lock-Leistungsabfall scheint so selten zu sein (weil der Anwendungsfall es so weit treibt), dass Sie bei Google kein einziges Ergebnis für den Namen der Spin-Lock-Funktion finden.
Das Upgrade auf eine API, die mehr Kontrolle über die Zuordnungen (Vulkan) bietet, ist in Arbeit, aber es ist nicht als kurzfristige Lösung geeignet. Der Wechsel zu einem besseren Betriebssystem-Kernel ist derzeit aus demselben Grund nicht möglich.
Die Reduzierung der CPU-Auslastung funktioniert auch nicht; Es gibt zu viel Arbeit, die anders als die (normalerweise triviale und kostengünstige) Pufferkopie erledigt werden muss.
Die Frage
Was kann getan werden?
Ich muss die Anzahl der einzelnen Seitenfehler deutlich reduzieren. Ich kenne die Adresse und Größe des Puffers, der meinem Prozess zugeordnet wurde, und ich weiß auch, dass der Speicher noch nicht festgeschrieben wurde.
Wie kann ich sicherstellen, dass der Speicher mit der geringstmöglichen Anzahl von Transaktionen ausgeführt wird?
Exotische Flags für DX11, die eine Aufhebung der Zuweisung der Puffer nach dem Unmapping verhindern würden, Windows-APIs, um Commit in einer einzigen Transaktion zu erzwingen, so ziemlich alles ist willkommen.
Der aktuelle Zustand
// In the processing threads
{
DX11DeferredContext->Map(..., &buffer)
std::memcpy(buffer, source, size);
DX11DeferredContext->Unmap(...);
}
es klingt wie Sie sind bei etwa 400 M für alle 16 Threads alle zusammen. Ziemlich niedrig. Können Sie überprüfen, ob Sie dies in Ihrer Anwendung nicht überschreiten? Was ist der Speicherverbrauch dort? Ich frage mich, ob Sie ein Speicherleck haben. – Serge
Der Spitzenverbrauch liegt bei 7-8GB, aber das ist normal, wenn man bedenkt, dass die gesamte Verarbeitungspipeline> 1s Pufferung benötigt, um alle Arten von Engpässen auszugleichen. Ja, es ist "nur" 400 MB, 25 mal pro Sekunde. Und es funktioniert gut, bis die Basis-CPU-Auslastung über 50% steigt und die Leistung der Spin Lock plötzlich von praktisch 0 auf ~ 40-50% der vollständigen CPU-Auslastung steigt. Beeinflusst gleichzeitig andere Prozesse im System. – Ext3h
1. Was ist Ihr physischer Speicher? Kannst du alle anderen aktiven Prozesse beenden? 2. Rate # 2, da du den 50% -Schwellenwert siehst, könnte es zu Problemen mit Hyper-Threading kommen. Wie viele physikalische Kerne hast du? 8? Kannst du Hyperthreading deaktivieren? Versuchen Sie, in Ihrem Fall auf einem sauberen Rechner so viele Threads auszuführen, wie es physische CPUs gibt. – Serge