Ich versuche, große Datenmengen auf meine SSD (Solid State Drive) zu schreiben. Und mit riesigen Mengen meine ich 80 GB.
Ich habe im Internet nach Lösungen gesucht, aber das Beste, was ich mir ausgedacht habe, war Folgendes:
#include <fstream>
const unsigned long long size = 64ULL*1024ULL*1024ULL;
unsigned long long a[size];
int main()
{
std::fstream myfile;
myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
//Here would be some error handling
for(int i = 0; i < 32; ++i){
//Some calculations to fill a[]
myfile.write((char*)&a,size*sizeof(unsigned long long));
}
myfile.close();
}
Dieses mit Visual Studio 2010 und vollständigen Optimierungen kompilierte und unter Windows 7 ausgeführte Programm erreicht eine maximale Geschwindigkeit von 20 MB / s. Was mich wirklich stört, ist, dass Windows Dateien von einer anderen SSD mit einer Geschwindigkeit zwischen 150 MB / s und 200 MB / s auf diese SSD kopieren kann. Also mindestens 7 mal schneller. Deshalb denke ich, ich sollte schneller gehen können.
Irgendwelche Ideen, wie ich mein Schreiben beschleunigen kann?
c++
performance
optimization
file-io
io
Dominic Hofer
quelle
quelle
fwrite()
konnte ich ungefähr 80% der maximalen Schreibgeschwindigkeiten erreichen. Nur mitFILE_FLAG_NO_BUFFERING
konnte ich jemals Höchstgeschwindigkeit erreichen.Antworten:
Dies hat den Job gemacht (im Jahr 2012):
Ich habe gerade 8 GB in 36 Sekunden eingestellt, was ungefähr 220 MB / s entspricht, und ich denke, das maximiert meine SSD. Es ist auch erwähnenswert, dass der Code in der Frage einen Kern zu 100% verwendete, während dieser Code nur 2-5% verwendet.
Vielen Dank an alle.
Update : 5 Jahre sind vergangen, es ist jetzt 2017. Compiler, Hardware, Bibliotheken und meine Anforderungen haben sich geändert. Deshalb habe ich einige Änderungen am Code vorgenommen und einige neue Messungen durchgeführt.
Zuerst den Code:
Dieser Code wird mit Visual Studio 2017 und g ++ 7.2.0 kompiliert (eine neue Anforderung). Ich habe den Code mit zwei Setups ausgeführt:
Dies ergab die folgenden Messungen (nachdem die Werte für 1 MB verworfen wurden, da es sich um offensichtliche Ausreißer handelte): Beide Male haben Option1 und Option3 meine SSD maximal genutzt. Ich hatte nicht damit gerechnet, da Option2 damals der schnellste Code auf meinem alten Computer war.
TL; DR : Meine Messungen zeigen, dass
std::fstream
über verwendet werden mussFILE
.quelle
FILE*
ist schneller als Streams. Ich hätte einen solchen Unterschied nicht erwartet, da er sowieso an die E / A gebunden sein sollte.ios::sync_with_stdio(false);
einen Unterschied für den Code mit Stream? Ich bin nur neugierig, wie groß der Unterschied zwischen der Verwendung dieser Leitung und der Nichtverwendung dieser Leitung ist, aber ich habe nicht die Festplatte, die schnell genug ist, um den Eckfall zu überprüfen. Und wenn es einen wirklichen Unterschied gibt.Versuchen Sie Folgendes in der angegebenen Reihenfolge:
Kleinere Puffergröße. Das Schreiben von ~ 2 MiB gleichzeitig könnte ein guter Anfang sein. Auf meinem letzten Laptop war ~ 512 KiB der Sweet Spot, aber ich habe noch nicht auf meiner SSD getestet.
Hinweis: Ich habe festgestellt, dass sehr große Puffer die Leistung beeinträchtigen . Ich habe zuvor Geschwindigkeitsverluste bei der Verwendung von 16-MiB-Puffern anstelle von 512-KiB-Puffern festgestellt.
Verwenden Sie
_open
(oder_topen
wenn Sie Windows-korrekt sein möchten), um die Datei zu öffnen, und verwenden Sie dann_write
. Dies wird wahrscheinlich viel Pufferung vermeiden, ist aber nicht sicher.Verwenden von Windows-spezifischen Funktionen wie
CreateFile
undWriteFile
. Dadurch wird eine Pufferung in der Standardbibliothek vermieden.quelle
FILE_FLAG_NO_BUFFERING
- in denen größere Puffer tendenziell besser sind. Da denke ichFILE_FLAG_NO_BUFFERING
ist so ziemlich DMA.Ich sehe keinen Unterschied zwischen std :: stream / FILE / device. Zwischen Pufferung und Nichtpufferung.
Beachten Sie auch:
Ich sehe den Code in 63 Sekunden ausgeführt.
Also eine Übertragungsrate von: 260M / s (meine SSD sieht etwas schneller aus als deine).
Ich erhalte keine Erhöhung, wenn ich von std :: fstream zu FILE * wechsle.
Der C ++ - Stream arbeitet also so schnell, wie es die zugrunde liegende Bibliothek zulässt.
Ich halte es jedoch für unfair, das Betriebssystem mit einer Anwendung zu vergleichen, die auf dem Betriebssystem basiert. Die Anwendung kann keine Annahmen treffen (sie weiß nicht, dass es sich bei den Laufwerken um SSD handelt) und verwendet daher die Dateimechanismen des Betriebssystems für die Übertragung.
Das Betriebssystem muss zwar keine Annahmen treffen. Es kann die Typen der beteiligten Laufwerke erkennen und die optimale Technik für die Übertragung der Daten verwenden. In diesem Fall eine direkte Speicher-zu-Speicher-Übertragung. Versuchen Sie, ein Programm zu schreiben, das 80G von einem Speicherort in einen anderen kopiert, und sehen Sie, wie schnell das ist.
Bearbeiten
Ich habe meinen Code geändert, um die Aufrufe der unteren Ebene zu verwenden:
dh keine Pufferung.
Dies machte keinen Unterschied.
HINWEIS : Mein Laufwerk ist ein SSD-Laufwerk. Wenn Sie ein normales Laufwerk haben, sehen Sie möglicherweise einen Unterschied zwischen den beiden oben genannten Techniken. Aber wie ich erwartet hatte, macht das Nichtpuffern und Puffern (wenn große Blöcke größer als die Puffergröße geschrieben werden) keinen Unterschied.
Bearbeiten 2:
Haben Sie die schnellste Methode zum Kopieren von Dateien in C ++ ausprobiert?
quelle
FILE*
.Die beste Lösung besteht darin, ein asynchrones Schreiben mit doppelter Pufferung zu implementieren.
Schauen Sie sich die Zeitleiste an:
Das 'F' steht für die Zeit zum Füllen des Puffers und das 'W' für die Zeit zum Schreiben des Puffers auf die Festplatte. Das Problem bei der Zeitverschwendung zwischen dem Schreiben von Puffern in eine Datei. Wenn Sie jedoch das Schreiben in einem separaten Thread implementieren, können Sie den nächsten Puffer sofort wie folgt füllen:
F - 1. Puffer füllen
f - 2. Puffer füllen
W - 1. Puffer in Datei
schreiben w - 2. Puffer in Datei schreiben
_ - warten, bis der Vorgang abgeschlossen ist
Dieser Ansatz mit Pufferswaps ist sehr nützlich, wenn das Füllen eines Puffers eine komplexere Berechnung erfordert (daher mehr Zeit). Ich implementiere immer eine CSequentialStreamWriter-Klasse, die asynchrones Schreiben im Inneren verbirgt, sodass die Schnittstelle für den Endbenutzer nur über Schreibfunktionen verfügt.
Die Puffergröße muss ein Vielfaches der Festplattenclustergröße sein. Andernfalls wird die Leistung schlecht, wenn Sie einen einzelnen Puffer in zwei benachbarte Festplattencluster schreiben.
Den letzten Puffer schreiben.
Wenn Sie die Schreibfunktion zum letzten Mal aufrufen, müssen Sie sicherstellen, dass der aktuelle Puffer, der gefüllt wird, auch auf die Festplatte geschrieben wird. Daher sollte CSequentialStreamWriter über eine separate Methode verfügen, z. B. Finalize (Final Buffer Flush), mit der der letzte Teil der Daten auf die Festplatte geschrieben werden soll.
Fehlerbehandlung.
Während der Code den zweiten Puffer füllt und der erste in einen separaten Thread geschrieben wird, das Schreiben jedoch aus irgendeinem Grund fehlschlägt, sollte der Hauptthread diesen Fehler kennen.
Nehmen wir an, die Schnittstelle eines CSequentialStreamWriter hat eine Write-Funktion, die bool zurückgibt oder eine Ausnahme auslöst. Wenn also ein Fehler in einem separaten Thread auftritt, müssen Sie sich diesen Status merken. Wenn Sie also das nächste Mal Write oder Finilize im Hauptthread aufrufen, wird die Methode zurückgegeben Falsch oder löst eine Ausnahme aus. Und es spielt keine Rolle, an welchem Punkt Sie aufgehört haben, einen Puffer zu füllen, selbst wenn Sie nach dem Fehler einige Daten vorausgeschrieben haben - höchstwahrscheinlich wäre die Datei beschädigt und unbrauchbar.
quelle
Ich würde vorschlagen, die Dateizuordnung zu versuchen . Ich habe
mmap
in der Vergangenheit in einer UNIX-Umgebung verwendet und war beeindruckt von der hohen Leistung, die ich erzielen konntequelle
Könnten Sie
FILE*
stattdessen verwenden und die Leistung messen, die Sie gewonnen haben? Einige Optionen sindfwrite/write
anstelle vonfstream
:Wenn Sie sich für die Verwendung entscheiden
write
, versuchen Sie etwas Ähnliches:Ich würde Ihnen auch raten, sich das anzuschauen
memory map
. Das könnte deine Antwort sein. Einmal musste ich eine 20-GB-Datei in einer anderen verarbeiten, um sie in der Datenbank zu speichern, und die Datei öffnete sich nicht einmal. Also die Lösung, um die Speicherkarte zu verwenden. Ich habe dasPython
aber gemacht.quelle
FILE*
erhält ein einfaches Äquivalent des Originalcodes, das denselben 512-MB-Puffer verwendet, die volle Geschwindigkeit. Ihr aktueller Puffer ist zu klein.2
entspricht dies einem Standardfehler, es wird jedoch weiterhin empfohlen,STDERR_FILENO
anstelle von zu verwenden2
. Ein weiteres wichtiges Problem ist, dass ein möglicher Fehler, den Sie erhalten können, EINTR ist. Wenn Sie ein Interrupt-Signal empfangen, ist dies kein wirklicher Fehler, und Sie sollten es einfach erneut versuchen.Versuchen Sie es mit API-Aufrufen open () / write () / close () und experimentieren Sie mit der Größe des Ausgabepuffers. Ich meine, nicht den gesamten "Viele-Viele-Bytes" -Puffer auf einmal übergeben, sondern ein paar Schreibvorgänge ausführen (dh TotalNumBytes / OutBufferSize). OutBufferSize kann zwischen 4096 Byte und Megabyte liegen.
Ein weiterer Versuch: Verwenden Sie WinAPI OpenFile / CreateFile und deaktivieren Sie mit diesem MSDN-Artikel die Pufferung (FILE_FLAG_NO_BUFFERING). Und dieser MSDN-Artikel über WriteFile () gezeigt, wie die Blockgröße für das Laufwerk ermittelt wird, um die optimale Puffergröße zu ermitteln.
Auf jeden Fall ist std :: ofstream ein Wrapper und möglicherweise werden E / A-Vorgänge blockiert. Beachten Sie, dass das Durchlaufen des gesamten N-Gigabyte-Arrays ebenfalls einige Zeit in Anspruch nimmt. Während Sie einen kleinen Puffer schreiben, gelangt dieser in den Cache und arbeitet schneller.
quelle
fstream
s sind an sich nicht langsamer als C-Streams, verbrauchen jedoch mehr CPU (insbesondere wenn die Pufferung nicht richtig konfiguriert ist). Wenn eine CPU gesättigt ist, wird die E / A-Rate begrenzt.Mindestens die MSVC 2015-Implementierung kopiert jeweils 1 Zeichen in den Ausgabepuffer, wenn kein Stream-Puffer festgelegt ist (siehe
streambuf::xsputn
). Stellen Sie daher sicher, dass Sie einen Stream-Puffer (> 0) festlegen .fstream
Mit diesem Code kann ich eine Schreibgeschwindigkeit von 1500 MB / s (die volle Geschwindigkeit meiner M.2-SSD) erreichen :Ich habe versucht , diesen Code auf anderen Plattformen (Ubuntu, FreeBSD) und bemerkte keine I / O - Rate Unterschiede, sondern eine CPU - Auslastung Differenz von etwa 8: 1 (
fstream
verwendet 8 - mal mehr CPU ). Man könnte sich also vorstellen, wenn ich eine schnellere Festplattefstream
hätte, würde sich das Schreiben früher verlangsamen als diestdio
Version.quelle
Versuchen Sie, Dateien mit Speicherzuordnung zu verwenden.
quelle
ReadFile
für den sequentiellen Zugriff so effektiv sind wie normales Lesen / Schreiben und dergleichen, obwohl sie für den wahlfreien Zugriff möglicherweise besser sind.Wenn Sie im Explorer etwas von Datenträger A auf Datenträger B kopieren, verwendet Windows DMA. Das bedeutet, dass die CPU für den größten Teil des Kopiervorgangs im Grunde nichts anderes tut, als dem Festplattencontroller mitzuteilen, wo Daten abgelegt und abgerufen werden sollen, wodurch ein ganzer Schritt in der Kette entfällt und einer, der überhaupt nicht für das Verschieben großer Mengen optimiert ist von Daten - und ich meine Hardware.
Was du tun, betrifft die CPU sehr. Ich möchte Sie auf den Teil "Einige Berechnungen zum Füllen eines []" verweisen. Was ich für wesentlich halte. Sie generieren ein [], kopieren dann von einem [] in einen Ausgabepuffer (genau das macht fstream :: write), generieren dann erneut usw.
Was ist zu tun? Multithreading! (Ich hoffe, Sie haben einen Multi-Core-Prozessor)
quelle
Wenn Sie schnell in Dateistreams schreiben möchten, können Sie den Lesepuffer für Streams vergrößern:
Auch wenn viele Daten in Dateien ist es manchmal schneller zu schreiben logisch erweitert die Dateigröße statt physisch, ist dies , weil , wenn logisch eine Datei des Dateisystem erstreckt nicht Null der neue Raum, bevor es zu schreiben. Es ist auch klug, die Datei logisch mehr zu erweitern, als Sie tatsächlich benötigen, um viele Dateierweiterungen zu verhindern. Die logische Dateierweiterung wird unter Windows durch Aufrufen
SetFileValidData
oderxfsctl
mitXFS_IOC_RESVSP64
auf XFS-Systemen unterstützt.quelle
Ich kompiliere mein Programm in gcc in GNU / Linux und mingw in Win 7 und Win XP und funktionierte gut
Sie können mein Programm verwenden und zum Erstellen einer 80-GB-Datei einfach die Zeile 33 in ändern
Wenn Sie das Programm beenden, wird die Datei zerstört. Überprüfen Sie die Datei, während sie ausgeführt wird
Um das gewünschte Programm zu haben, ändern Sie einfach das Programm
Das erste ist das Windows-Programm und das zweite ist für GNU / Linux
http://mustafajf.persiangig.com/Projects/File/WinFile.cpp
http://mustafajf.persiangig.com/Projects/File/File.cpp
quelle