Ich interessiere mich insbesondere für C++. Da Linux jedoch in C geschrieben ist, kann es sein, dass C diesen Anwendungsfall besser unterstützt als C++.
Man sollte beachten, dass C++ sowohl Stream-basiertes IO als Teil der Standardbibliothek als auch C-Style-IO bereitstellt. cppreference
Es ist auch interessant festzustellen, dass Rust-Datei-IO standardmäßig ungepuffert ist. Es gibt keinen User-Space-Puffer, es sei denn, der Benutzer fordert einen an, indem er die Datei-E/A in einen gepufferten Reader oder gepufferten Writer-Typ einschließt.
Standardmäßig erstellen sowohl der C-Stil als auch die C++-Streams-E/A sowohl einen User-Space-Puffer als auch einen Kernel-Puffer. Ich würde vermuten, dass der Kernel-Puffer eine Kernel-eigene Sache ist, die nicht ausgeschaltet werden kann. Der Zweck eines User-Space-Puffers besteht darin, wiederholte Betriebssystemaufrufe zu vermeiden, wenn eine Lese- oder Schreibfunktion aus dem User-Space ausgelöst wird.
Ohne diesen User-Space-Puffer müsste jeder Lese- und Schreibvorgang ein Systemaufruf sein, der den Kernel-Puffer berührt. Mit dem User-Space-Puffer können Lese- und Schreibaufrufe einen Puffer im Speicher ändern, auf den die Anwendung direkten Zugriff hat und der nicht über das Betriebssystem erfolgen muss, was die Latenz erhöhen würde.
Der Nachteil besteht jedoch darin, dass das Vorhandensein von zwei Puffern bedeutet, dass Daten im Rahmen eines Lese- oder Schreibvorgangs auf die Festplatte mindestens zweimal kopiert werden. Daher kann es dort zu Leistungseinbußen kommen, insbesondere im Zusammenhang mit dem Lesen und Schreiben von Daten in großen Blöcken.
Also ein paar Fragen.
- Wenn Sie das Rust-Verhalten wünschen, Datei-IO standardmäßig ohne zusätzlichen Benutzerspeicherplatzpuffer, was muss man tun?
- Inwiefern unterscheidet sich dies im Hinblick auf die erwartete Leistung von der Verwendung von speicherzugeordneten IO?
Es kann auch hilfreich sein, wenn ich erkläre, warum ich diese Frage stelle. Mein Kollege scheint zu glauben, dass die Verwendung von Memory Mapped IO ein Wundermittel für die Leistung ist. Allerdings glaube ich nicht an solche Wundermittel. (Zu Ihrer Information, mein Kollege ist kein Experte auf diesem Gebiet, er hat nur diese besondere Überzeugung, weil er zufällig in der Vergangenheit ein Softwareprogramm verwendet hat, das Memory Mapped IO als Teil seines Marketingmaterials verwendet hat.)
Meine Annahme wäre, dass für die Ausführung von Block-IO die Speicherzuordnung wahrscheinlich keinen nennenswerten Leistungsvorteil gegenüber dem Aufruf von read oder write für ein Dateihandle bietet, das nicht über den zusätzlichen User-Space-Puffer verfügt.
Anders ausgedrückt: Wenn die Speicherzuordnung standardmäßig den User-Space-Puffer vermeidet, wenn der User-Space-Puffer dann aus E/A-Vorgängen entfernt werden kann, die standardmäßig einen User-Space-Puffer erfordern würden, warum sollte dann die Leistung solcher Vorgänge nicht mit der speicherzugeordneten Datei-E/A vergleichbar sein?
Wenn Sie eine völlig spezifische Anwendung wünschen, stellen Sie sich vor, ich schreibe ein Programm, das beim Start ein Array von 10^9 64-Bit-Ganzzahlen aus einer Datei lesen muss, und warten Sie etwa eine Weile auf Daten Um Daten von einem Netzwerk-Socket zu streamen, hängen Sie nach und nach Daten an dieses Array im Speicher an und schreiben Sie dieses jetzt größere Array von Ganzzahlen vor dem Herunterfahren erneut auf die Festplatte. Warum sollten Sie dafür nicht normale Lese- und Schreibfunktionen verwenden, wenn der User-Space-Puffer ausgeschaltet werden kann?
Ich habe etwas mehr gelernt. Es scheint, dass das Äquivalent zu Rusts ungepuffertem IO Lese-- und Schreib--Betriebssystemaufrufe sind, obwohl diese Linux-spezifisch und nicht plattformübergreifend sind wie Rust.
Es ist auch nützlich zu klären, wie viele Puffer an den verschiedenen Optionen beteiligt sind:
- /
Code: Select all
read: Festplatte -> Kernel-Puffer -> Vom Benutzer zugewiesene DatenstrukturCode: Select all
write - /
Code: Select all
fread, C++ iostreams: Festplatte -> Kernel-Puffer -> Versteckter Benutzerraumpuffer -> Vom Benutzer zugewiesene DatenstrukturCode: Select all
fwrite - Rust-Datei: Wie gelesen/
Code: Select all
write - Rust-Datei, mit Puffer-Wrapper: Das Gleiche wie C++ iostreams
- Mmap: Disk -> Kernel-Puffer (den Sie dann direkt aus dem User-Space-Code manipulieren)
- : Festplatte -> vom Benutzer verwalteter Puffer. Dieses ist etwas Besonderes, da es von Ihnen erwartet, dass Sie den gesamten IO-Stack, den der Kernel normalerweise verwalten würde, im User-Space-Code implementieren. Für außergewöhnliche Anwendungsfälle. Wahrscheinlich nicht das, was Sie wollen. Sicherlich nicht gleichbedeutend mit Rusts ungepuffertem (Standard-) std::File
Code: Select all
O_DIRECT
Code: Select all
writeBeachten Sie außerdem: Sie können in Rust keine speicherzugeordneten Datei-IOs verwenden. (Es sei denn, Sie sind bereit, unsicheren Code zu schreiben.)
Mobile version