it-swarm.com.de

std :: fstream buffering vs manual buffering (warum 10-fache Verstärkung mit manueller Pufferung)?

Ich habe zwei Schreibkonfigurationen getestet:

1) Fstream-Pufferung:

// Initialization
const unsigned int length = 8192;
char buffer[length];
std::ofstream stream;
stream.rdbuf()->pubsetbuf(buffer, length);
stream.open("test.dat", std::ios::binary | std::ios::trunc)

// To write I use :
stream.write(reinterpret_cast<char*>(&x), sizeof(x));

2) Manuelle Pufferung:

// Initialization
const unsigned int length = 8192;
char buffer[length];
std::ofstream stream("test.dat", std::ios::binary | std::ios::trunc);

// Then I put manually the data in the buffer

// To write I use :
stream.write(buffer, length);

Ich habe das gleiche Ergebnis erwartet ...

Aber meine manuelle Pufferung verbessert die Leistung um den Faktor 10, um eine Datei mit 100 MB zu schreiben, und die Pufferung mit Fstream ändert nichts im Vergleich zur normalen Situation (ohne einen neuen Puffer neu zu definieren).

Hat jemand eine Erklärung für diese Situation?

EDIT: Hier sind die Neuigkeiten: ein Benchmark, der gerade auf einem Supercomputer erstellt wurde (Linux-64-Bit-Architektur, Intel Xeon 8-Core, Lustre-Dateisystem und ... hoffentlich gut konfigurierte Compiler).benchmark (und ich erkläre nicht den Grund der "Resonanz" für einen manuellen 1-KB-Puffer ...)

EDIT 2: Und die Resonanz bei 1024 B (wenn jemand eine Idee hat, bin ich interessiert): enter image description here

50
Vincent

Dies ist im Wesentlichen auf Funktionsaufruf und Indirektion zurückzuführen. Die ofstream :: write () -Methode wird von ostream geerbt. Diese Funktion ist nicht in libstdc ++ eingebettet, was die erste Quelle für Overhead ist. Dann muss ostream :: write () rdbuf () -> sputn () aufrufen, um das eigentliche Schreiben auszuführen. Dies ist ein virtueller Funktionsaufruf.

Darüber hinaus leitet libstdc ++ sputn () an eine andere virtuelle Funktion xsputn () um, die einen weiteren virtuellen Funktionsaufruf hinzufügt.

Wenn Sie die Zeichen selbst in den Puffer legen, können Sie diesen Aufwand vermeiden.

24
Vaughn Cato

Ich möchte erklären, was die Ursache des Peaks in der second chart ist

Tatsächlich führen virtuelle Funktionen, die von std::ofstream verwendet werden, dazu, dass der Performace abnimmt, wie wir auf dem ersten Bild sehen, aber es gibt keine Antwort darauf, warum die Performance am höchsten war, als die manuelle Puffergröße weniger als 1024 Bytes betrug.

Das Problem betrifft die hohen Kosten des Systemaufrufs writev() und write()) und die interne Implementierung der internen Klasse std::filebuf von std::ofstream.

Um zu zeigen, wie write() die Leistung beeinflusst, habe ich einen einfachen Test mit dem dd-Tool auf meinem Linux-Computer durchgeführt, um 10 MB-Dateien mit unterschiedlichen Puffergrößen zu kopieren (Option bs):

[email protected]$ time dd if=/dev/zero of=zero bs=256 count=40000
40000+0 records in
40000+0 records out
10240000 bytes (10 MB) copied, 2.36589 s, 4.3 MB/s

real    0m2.370s
user    0m0.000s
sys     0m0.952s
test$test: time dd if=/dev/zero of=zero bs=512 count=20000
20000+0 records in
20000+0 records out
10240000 bytes (10 MB) copied, 1.31708 s, 7.8 MB/s

real    0m1.324s
user    0m0.000s
sys     0m0.476s

[email protected]: time dd if=/dev/zero of=zero bs=1024 count=10000
10000+0 records in
10000+0 records out
10240000 bytes (10 MB) copied, 0.792634 s, 12.9 MB/s

real    0m0.798s
user    0m0.008s
sys     0m0.236s

[email protected]: time dd if=/dev/zero of=zero bs=4096 count=2500
2500+0 records in
2500+0 records out
10240000 bytes (10 MB) copied, 0.274074 s, 37.4 MB/s

real    0m0.293s
user    0m0.000s
sys     0m0.064s

Wie Sie sehen können, ist der Puffer weniger, die Schreibgeschwindigkeit ist geringer und die dd viel Zeit im Systemspeicher verbraucht. Die Lese-/Schreibgeschwindigkeit nimmt also ab, wenn die Puffergröße abnimmt.

Aber warum war die höchste Geschwindigkeit, als die manuelle Puffergröße in den manuellen Puffertests zum Erstellen von Themen weniger als 1024 Byte betrug? Warum war es fast konstant?

Die Erklärung bezieht sich auf die std::ofstream-Implementierung, insbesondere auf den std::basic_filebuf.

Standardmäßig verwendet es 1024 Byte Puffer (Variable BUFSIZ). Wenn Sie also Daten mit weniger als 1024 Daten schreiben, wird der Systemaufruf writev() (nicht write()) mindestens einmal für zwei ofstream::write() -Operationen aufgerufen (Frieden hat die Größe 1023 <1024 - zuerst wird in den Puffer geschrieben, und zweitens erzwingt das Schreiben von erstem und zweitem). Daraus können wir schließen, dass ofstream::write() Geschwindigkeit nicht vor der Spitze von der manuellen Puffergröße abhängt (write() wird mindestens zweimal selten aufgerufen).

Wenn Sie versuchen, mit dem Aufruf von ofstream::write() einen Puffer größer oder gleich 1024 Byte zu schreiben, wird für jeden ofstream::write der Systemaufruf writev() aufgerufen. Sie sehen also, dass die Geschwindigkeit steigt, wenn der manuelle Puffer größer als 1024 ist (nach dem Peak).

Wenn Sie std::ofstream buffer mit streambuf::pubsetbuf() auf einen Puffer größer als 1024 (z. B. 8192 Bytes buffer) setzen und ostream::write() zum Schreiben von Daten mit 1024-er Größe verwenden möchten, wäre das Schreiben überraschend Die Geschwindigkeit ist die gleiche, wie Sie 1024-Puffer verwenden. Dies liegt daran, dass Implementierung von std::basic_filebuf - die interne Klasse von std::ofstream - hart zu forcieren Aufrufsystem writev()-Aufruf für jeden ofstream::write()-Aufruf ist, wenn der Puffer übergeben wird größer oder gleich 1024 Bytes (siehe basic_filebuf :: xsputn () Quellcode). Es gibt auch ein offenes Problem in der GCC Bugzilla, das am 2014-11-05 gemeldet wurde.

Die Lösung dieses Problems kann also in zwei möglichen Fällen erfolgen:

  • std::filebuf durch deine eigene Klasse ersetzen und std::ofstream neu definieren
  • geben Sie einen Puffer an, der an ofstream::write(), an die Peaces mit weniger als 1024 übergeben werden muss, und übergeben Sie sie nacheinander an ofstream::write()
  • Übergeben Sie keine kleinen Datenbereiche an ofstream::write(), um die Leistung der virtuellen Funktionen von std::ofstream nicht zu beeinträchtigen.
3
nomad85

Ich möchte zu den vorhandenen Antworten hinzufügen, dass dieses Leistungsverhalten (der gesamte Aufwand durch Aufrufen der virtuellen Methode/Indirektion) in der Regel kein Problem darstellt, wenn große Datenblöcke geschrieben werden. Was offenbar aus der Frage und diesen früheren Antworten weggelassen wurde (obwohl wahrscheinlich implizit verstanden wurde), ist, dass der ursprüngliche Code jedes Mal eine kleine Anzahl von Bytes schrieb. Nur zur Klarstellung für andere: Wenn Sie große Datenblöcke (~ kB +) schreiben, besteht kein Grund zu der Annahme, dass die manuelle Pufferung einen signifikanten Leistungsunterschied zur Verwendung der Pufferung von std::fstream haben wird.

0
wolf1oo