öffnen Wie kann man reservierten Speicher in C++ bereitstellen?



sqf syntax checker (1)

Aktuelle Problemumgehung, vereinfachter Pseudocode:

// During startup
{
    SetProcessWorkingSetSize(GetCurrentProcess(), 2*1024*1024*1024, -1);
}
// In the DX11 render loop thread
{
    DX11context->Map(..., &resource)
    VirtualLock(resource.pData, resource.size);
    notify();
    wait();
    DX11context->Unmap(...);
}
// In the processing threads
{
    wait();
    std::memcpy(buffer, source, size);
    signal();
}

VirtualLock() zwingt den Kernel, den angegebenen Adressbereich sofort mit RAM zu sichern. Der Aufruf der ergänzenden Funktion VirtualUnlock() ist optional und erfolgt implizit (und ohne zusätzliche Kosten), wenn der Adressbereich aus dem Prozess nicht zugeordnet wird. (Wenn es explizit aufgerufen wird, kostet es etwa 1/3 der Sperrkosten.)

Damit VirtualLock() funktionieren kann, muss SetProcessWorkingSetSize() zuerst aufgerufen werden, da die Summe aller von VirtualLock() gesperrten Speicherbereiche die für den Prozess konfigurierte minimale Arbeitssatzgröße nicht überschreiten darf. Wenn Sie die Mindestgröße für den Arbeitssatz auf einen höheren Wert als den Basisspeicherbedarf Ihres Prozesses festlegen, treten keine Nebenwirkungen auf, es sei denn, Ihr System ist potenziell austauschbar. Ihr Prozess wird immer noch nicht mehr RAM als die tatsächliche Arbeitssatzgröße verbrauchen.

Allein die Verwendung von VirtualLock() , wenn auch in einzelnen Threads und mit verzögerten DX11-Kontexten für Map / Unmap Aufrufe, reduzierte die Leistungseinbuße sofort von 40-50% auf etwas akzeptablere 15%.

Das Verwerfen der Verwendung eines verzögerten Kontextes und das ausschließliche Auslösen aller weichen Fehler sowie die entsprechende Aufhebung der Zuweisung beim Aufteilen auf einen einzelnen Thread gaben den erforderlichen Leistungsschub. Die Gesamtkosten für dieses Spin-Lock liegen nun bei <1% der gesamten CPU-Auslastung.

Zusammenfassung?

Wenn Sie unter Windows weiche Fehler erwarten, versuchen Sie, sie alle im selben Thread zu behalten. Die Durchführung eines Parallelspeichers selbst ist unproblematisch, in manchen Situationen sogar notwendig, um die Speicherbandbreite voll auszunutzen. Dies ist jedoch nur dann der Fall, wenn der Speicher bereits für RAM reserviert ist. VirtualLock() ist der effizienteste Weg, dies sicherzustellen.

(Wenn Sie nicht mit einer API wie DirectX arbeiten, die Speicher in Ihren Prozess einbindet, ist es unwahrscheinlich, dass nicht festgelegter Speicher häufig auftritt. Wenn Sie nur mit C ++ - new oder malloc Standard arbeiten, wird Ihr Speicher trotzdem gepoolt und recycelt Fehler sind selten.)

Stellen Sie sicher, dass Sie bei der Arbeit mit Windows keine Form von gleichzeitigen Seitenfehlern vermeiden.

Die allgemeine Situation

Eine Anwendung, die sowohl bei der Bandbreite als auch bei der CPU-Auslastung und der GPU-Nutzung extrem intensiv ist, muss etwa 10-15 GB pro Sekunde von einer GPU auf eine andere ü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-Edition von Windows 10.

Das eigentliche Problem

Sobald ich ~ 50% CPU-Last erreiche, MmPageFault() etwas in MmPageFault() (innerhalb des Windows-Kernels, das beim Zugriff auf Speicher aufgerufen wird, der in deinen Adressraum gemappt wurde, aber vom OS noch nicht festgeschrieben wurde), schrecklich ab und die restlichen 50% In MmPageFault() CPU-Last für eine Spin-Sperre MmPageFault() . Die CPU wird zu 100% ausgelastet und die Anwendungsleistung verschlechtert sich vollständig.

Ich muss annehmen, dass dies auf die immense Menge an Speicher zurückzuführen 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 von Aufrufen von MmPageFault() pro Sekunde, die sequentiell MmPageFault() , während memcpy() sequentiell in den Puffer schreibt. Für jede einzelne nicht festgelegte Seite.

Wenn die CPU-Last über 50% hinausgeht, verschlechtert sich der optimistische Spin-Lock im Windows-Kernel, der das Seitenmanagement schützt, vollständig leistungsmäßig.

Überlegungen

Der Puffer wird vom DX11-Treiber zugewiesen. 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 (Zuordnung / Aufheben der Zuordnung der Puffer) erfolgen alle aus einem einzigen Thread. Die eigentlichen Kopiervorgänge erfolgen möglicherweise multi-threaded über mehrere Threads als es virtuelle Prozessoren im System gibt.

Eine Reduzierung 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.

Die Verringerung der Spin-Lock-Leistung 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.

Das Reduzieren der CPU-Last 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 erheblich 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 das 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(...);
}




directx-11