Schreiben einer Binärdatei in C ++ sehr schnell

240

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?

Dominic Hofer
quelle
11
Haben Ihre Timing-Ergebnisse die Zeit ausgeschlossen, die Sie für Ihre Berechnungen benötigen, um ein [] zu füllen?
catchmeifyoutry
7
Ich habe diese Aufgabe schon einmal gemacht. Mit simple fwrite()konnte ich ungefähr 80% der maximalen Schreibgeschwindigkeiten erreichen. Nur mit FILE_FLAG_NO_BUFFERINGkonnte ich jemals Höchstgeschwindigkeit erreichen.
Mysticial
10
Ich bin mir nicht sicher, ob es fair ist, das Schreiben Ihrer Datei mit einer SSD-zu-SSD-Kopie zu vergleichen. Es kann durchaus sein, dass SSD-zu-SSD auf einer niedrigeren Ebene arbeitet, die C ++ - Bibliotheken vermeidet oder direkten Speicherzugriff (DMA) verwendet. Das Kopieren von Objekten ist nicht dasselbe wie das Schreiben beliebiger Werte in eine Datei mit wahlfreiem Zugriff.
Igor F.
4
@IgorF.: Das ist nur falsche Spekulation; Es ist ein vollkommen fairer Vergleich (wenn nichts anderes, zugunsten des Schreibens von Dateien). Das Kopieren über ein Laufwerk in Windows erfolgt nur durch Lesen und Schreiben. Darunter ist nichts Besonderes / Kompliziertes / Anderes los.
user541686
5
@ MaximYegorushkin: Link oder es ist nicht passiert. : P
user541686

Antworten:

232

Dies hat den Job gemacht (im Jahr 2012):

#include <stdio.h>
const unsigned long long size = 8ULL*1024ULL*1024ULL;
unsigned long long a[size];

int main()
{
    FILE* pFile;
    pFile = fopen("file.binary", "wb");
    for (unsigned long long j = 0; j < 1024; ++j){
        //Some calculations to fill a[]
        fwrite(a, 1, size*sizeof(unsigned long long), pFile);
    }
    fclose(pFile);
    return 0;
}

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:

#include <fstream>
#include <chrono>
#include <vector>
#include <cstdint>
#include <numeric>
#include <random>
#include <algorithm>
#include <iostream>
#include <cassert>

std::vector<uint64_t> GenerateData(std::size_t bytes)
{
    assert(bytes % sizeof(uint64_t) == 0);
    std::vector<uint64_t> data(bytes / sizeof(uint64_t));
    std::iota(data.begin(), data.end(), 0);
    std::shuffle(data.begin(), data.end(), std::mt19937{ std::random_device{}() });
    return data;
}

long long option_1(std::size_t bytes)
{
    std::vector<uint64_t> data = GenerateData(bytes);

    auto startTime = std::chrono::high_resolution_clock::now();
    auto myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
    myfile.write((char*)&data[0], bytes);
    myfile.close();
    auto endTime = std::chrono::high_resolution_clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}

long long option_2(std::size_t bytes)
{
    std::vector<uint64_t> data = GenerateData(bytes);

    auto startTime = std::chrono::high_resolution_clock::now();
    FILE* file = fopen("file.binary", "wb");
    fwrite(&data[0], 1, bytes, file);
    fclose(file);
    auto endTime = std::chrono::high_resolution_clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}

long long option_3(std::size_t bytes)
{
    std::vector<uint64_t> data = GenerateData(bytes);

    std::ios_base::sync_with_stdio(false);
    auto startTime = std::chrono::high_resolution_clock::now();
    auto myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
    myfile.write((char*)&data[0], bytes);
    myfile.close();
    auto endTime = std::chrono::high_resolution_clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}

int main()
{
    const std::size_t kB = 1024;
    const std::size_t MB = 1024 * kB;
    const std::size_t GB = 1024 * MB;

    for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option1, " << size / MB << "MB: " << option_1(size) << "ms" << std::endl;
    for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option2, " << size / MB << "MB: " << option_2(size) << "ms" << std::endl;
    for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option3, " << size / MB << "MB: " << option_3(size) << "ms" << std::endl;

    return 0;
}

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:

  • Laptop, Core i7, SSD, Ubuntu 16.04, g ++ Version 7.2.0 mit -std = c ++ 11 -march = native -O3
  • Desktop, Core i7, SSD, Windows 10, Visual Studio 2017 Version 15.3.1 mit / Ox / Ob2 / Oi / Ot / GT / GL / Gy

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.Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

TL; DR : Meine Messungen zeigen, dass std::fstreamüber verwendet werden muss FILE.

Dominic Hofer
quelle
8
+1 Ja, das war das erste, was ich versucht habe. FILE*ist schneller als Streams. Ich hätte einen solchen Unterschied nicht erwartet, da er sowieso an die E / A gebunden sein sollte.
Mysticial
12
Können wir daraus schließen, dass E / A im C-Stil (seltsamerweise) viel schneller sind als C ++ - Streams?
SChepurin
21
@ SChepurin: Wenn Sie pedantisch sind, wahrscheinlich nicht. Wenn Sie praktisch sind, wahrscheinlich ja. :)
user541686
10
Könnten Sie bitte (für einen C ++ - Dummkopf wie mich) den Unterschied zwischen den beiden Ansätzen erklären und warum dieser so viel schneller funktioniert als das Original?
Mike Chamberlain
11
Macht das Voranstellen 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.
Artur Czajka
24

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 _topenwenn 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 CreateFileund WriteFile. Dadurch wird eine Pufferung in der Standardbibliothek vermieden.

user541686
quelle
Überprüfen Sie alle online veröffentlichten Benchmark-Ergebnisse. Sie benötigen entweder 4-KB-Schreibvorgänge mit einer Warteschlangentiefe von 32 oder mehr oder Schreibvorgänge mit 512 KB oder mehr, um einen angemessenen Durchsatz zu erzielen.
Ben Voigt
@ BenVoigt: Ja, das korreliert damit, dass ich sagte, 512 KiB sei der Sweet Spot für mich. :)
user541686
Ja. Nach meiner Erfahrung sind kleinere Puffergrößen normalerweise optimal. Die Ausnahme ist, wenn Sie verwenden FILE_FLAG_NO_BUFFERING- in denen größere Puffer tendenziell besser sind. Da denke ich FILE_FLAG_NO_BUFFERINGist so ziemlich DMA.
Mysticial
22

Ich sehe keinen Unterschied zwischen std :: stream / FILE / device. Zwischen Pufferung und Nichtpufferung.

Beachten Sie auch:

  • SSD-Laufwerke "neigen" dazu, langsamer zu werden (niedrigere Übertragungsraten), wenn sie voll sind.
  • SSD-Laufwerke "neigen" dazu, langsamer zu werden (niedrigere Übertragungsraten), wenn sie älter werden (wegen nicht funktionierender Bits).

Ich sehe den Code in 63 Sekunden ausgeführt.
Also eine Übertragungsrate von: 260M / s (meine SSD sieht etwas schneller aus als deine).

64 * 1024 * 1024 * 8 /*sizeof(unsigned long long) */ * 32 /*Chunks*/

= 16G
= 16G/63 = 260M/s

Ich erhalte keine Erhöhung, wenn ich von std :: fstream zu FILE * wechsle.

#include <stdio.h>

using namespace std;

int main()
{
    
    FILE* stream = fopen("binary", "w");

    for(int loop=0;loop < 32;++loop)
    {
         fwrite(a, sizeof(unsigned long long), size, stream);
    }
    fclose(stream);

}

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.

#include <fcntl.h>
#include <unistd.h>


const unsigned long long size = 64ULL*1024ULL*1024ULL;
unsigned long long a[size];
int main()
{
    int data = open("test", O_WRONLY | O_CREAT, 0777);
    for(int loop = 0; loop < 32; ++loop)
    {   
        write(data, a, size * sizeof(unsigned long long));
    }   
    close(data);
}

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?

int main()
{
    std::ifstream  input("input");
    std::ofstream  output("ouptut");

    output << input.rdbuf();
}
Martin York
quelle
5
Ich habe nicht abgestimmt, aber Ihre Puffergröße ist zu klein. Ich habe es mit dem gleichen 512 MB Puffer gemacht, den das OP verwendet, und ich bekomme 20 MB / s mit Streams gegenüber 90 MB / s mit FILE*.
Mysticial
Auch dein Weg mit fwrite (a, sizeof (unsigned long long), size, stream); anstelle von fwrite (a, 1, size * sizeof (unsigned long long), pFile); gibt mir 220MB / s mit Blöcken von 64MB pro Schreibvorgang.
Dominic Hofer
2
@Mysticial: Es überrascht mich, dass die Puffergröße einen Unterschied macht (obwohl ich Ihnen glaube). Der Puffer ist nützlich, wenn Sie viele kleine Schreibvorgänge haben, damit das zugrunde liegende Gerät nicht mit vielen Anforderungen belästigt wird. Wenn Sie jedoch große Blöcke schreiben, ist beim Schreiben / Lesen (auf einem blockierenden Gerät) kein Puffer erforderlich. Daher sollten die Daten direkt an das zugrunde liegende Gerät übergeben werden (wodurch der Puffer umgangen wird). Wenn Sie jedoch einen Unterschied sehen, würde dies dem widersprechen und mich wundern, warum der Schreibvorgang überhaupt einen Puffer verwendet.
Martin York
2
Die beste Lösung besteht NICHT darin, die Puffergröße zu erhöhen, sondern den Puffer zu entfernen und die Daten durch Schreiben direkt an das zugrunde liegende Gerät weiterzuleiten.
Martin York
1
@Mysticial: 1) Es gibt keine kleinen Stücke => Es ist immer groß genug (in diesem Beispiel). In diesem Fall sind die Chunks 512 MB groß. 2) Dies ist ein SSD-Laufwerk (sowohl meins als auch das OP), sodass nichts davon relevant ist. Ich habe meine Antwort aktualisiert.
Martin York
13

Die beste Lösung besteht darin, ein asynchrones Schreiben mit doppelter Pufferung zu implementieren.

Schauen Sie sich die Zeitleiste an:

------------------------------------------------>
FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW|

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:

------------------------------------------------> (main thread, fills buffers)
FF|ff______|FF______|ff______|________|
------------------------------------------------> (writer thread)
  |WWWWWWWW|wwwwwwww|WWWWWWWW|wwwwwwww|

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.

------------------------------------------------> (main thread, fills buffers)
FF|fX|
------------------------------------------------> (writer thread)
__|X|

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.

HandMadeOX
quelle
3
Das parallele Ausführen von E / A mit Berechnungen ist eine sehr gute Idee, aber unter Windows sollten Sie keine Threads verwenden, um dies zu erreichen. Verwenden Sie stattdessen "Überlappende E / A", wodurch einer Ihrer Threads während des E / A-Aufrufs nicht blockiert wird. Dies bedeutet, dass Sie sich kaum um die Thread-Synchronisierung kümmern müssen (greifen Sie einfach nicht auf einen Puffer zu, der eine aktive E / A-Operation verwendet).
Ben Voigt
11

Ich würde vorschlagen, die Dateizuordnung zu versuchen . Ich habe mmapin der Vergangenheit in einer UNIX-Umgebung verwendet und war beeindruckt von der hohen Leistung, die ich erzielen konnte

Ralph
quelle
1
@nalply Es ist immer noch eine funktionierende, effiziente und interessante Lösung.
Yam Marcovic
stackoverflow.com/a/2895799/220060 über die Vor- und Nachteile von mmap. Beachten Sie insbesondere "Für reine sequenzielle Zugriffe auf die Datei ist es auch nicht immer die bessere Lösung [...]". Auch stackoverflow.com/questions/726471 besagt effektiv, dass Sie auf einem 32-Bit-System auf 2 oder 2 beschränkt sind 3 GB. - Übrigens bin ich es nicht, der diese Antwort abgelehnt hat.
Nalply
8

Könnten Sie FILE*stattdessen verwenden und die Leistung messen, die Sie gewonnen haben? Einige Optionen sind fwrite/writeanstelle von fstream:

#include <stdio.h>

int main ()
{
  FILE * pFile;
  char buffer[] = { 'x' , 'y' , 'z' };
  pFile = fopen ( "myfile.bin" , "w+b" );
  fwrite (buffer , 1 , sizeof(buffer) , pFile );
  fclose (pFile);
  return 0;
}

Wenn Sie sich für die Verwendung entscheiden write, versuchen Sie etwas Ähnliches:

#include <unistd.h>
#include <fcntl.h>

int main(void)
{
    int filedesc = open("testfile.txt", O_WRONLY | O_APPEND);

    if (filedesc < 0) {
        return -1;
    }

    if (write(filedesc, "This will be output to testfile.txt\n", 36) != 36) {
        write(2, "There was an error writing to testfile.txt\n", 43);
        return -1;
    }

    return 0;
}

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 das Pythonaber gemacht.

Cybertextron
quelle
Tatsächlich FILE*erhält ein einfaches Äquivalent des Originalcodes, das denselben 512-MB-Puffer verwendet, die volle Geschwindigkeit. Ihr aktueller Puffer ist zu klein.
Mysticial
1
@Mysticial Aber das ist nur ein Beispiel.
Cybertextron
In den meisten Systemen 2entspricht dies einem Standardfehler, es wird jedoch weiterhin empfohlen, STDERR_FILENOanstelle von zu verwenden 2. 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.
Peyman
6

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.

Viktor Latypov
quelle
6

fstreams 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 .

fstreamMit diesem Code kann ich eine Schreibgeschwindigkeit von 1500 MB / s (die volle Geschwindigkeit meiner M.2-SSD) erreichen :

#include <iostream>
#include <fstream>
#include <chrono>
#include <memory>
#include <stdio.h>
#ifdef __linux__
#include <unistd.h>
#endif
using namespace std;
using namespace std::chrono;
const size_t sz = 512 * 1024 * 1024;
const int numiter = 20;
const size_t bufsize = 1024 * 1024;
int main(int argc, char**argv)
{
  unique_ptr<char[]> data(new char[sz]);
  unique_ptr<char[]> buf(new char[bufsize]);
  for (size_t p = 0; p < sz; p += 16) {
    memcpy(&data[p], "BINARY.DATA.....", 16);
  }
  unlink("file.binary");
  int64_t total = 0;
  if (argc < 2 || strcmp(argv[1], "fopen") != 0) {
    cout << "fstream mode\n";
    ofstream myfile("file.binary", ios::out | ios::binary);
    if (!myfile) {
      cerr << "open failed\n"; return 1;
    }
    myfile.rdbuf()->pubsetbuf(buf.get(), bufsize); // IMPORTANT
    for (int i = 0; i < numiter; ++i) {
      auto tm1 = high_resolution_clock::now();
      myfile.write(data.get(), sz);
      if (!myfile)
        cerr << "write failed\n";
      auto tm = (duration_cast<milliseconds>(high_resolution_clock::now() - tm1).count());
      cout << tm << " ms\n";
      total += tm;
    }
    myfile.close();
  }
  else {
    cout << "fopen mode\n";
    FILE* pFile = fopen("file.binary", "wb");
    if (!pFile) {
      cerr << "open failed\n"; return 1;
    }
    setvbuf(pFile, buf.get(), _IOFBF, bufsize); // NOT important
    auto tm1 = high_resolution_clock::now();
    for (int i = 0; i < numiter; ++i) {
      auto tm1 = high_resolution_clock::now();
      if (fwrite(data.get(), sz, 1, pFile) != 1)
        cerr << "write failed\n";
      auto tm = (duration_cast<milliseconds>(high_resolution_clock::now() - tm1).count());
      cout << tm << " ms\n";
      total += tm;
    }
    fclose(pFile);
    auto tm2 = high_resolution_clock::now();
  }
  cout << "Total: " << total << " ms, " << (sz*numiter * 1000 / (1024.0 * 1024 * total)) << " MB/s\n";
}

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 ( fstreamverwendet 8 - mal mehr CPU ). Man könnte sich also vorstellen, wenn ich eine schnellere Festplatte fstreamhätte, würde sich das Schreiben früher verlangsamen als die stdioVersion.

Rustyx
quelle
3

Versuchen Sie, Dateien mit Speicherzuordnung zu verwenden.

qehgt
quelle
@Mehrdad aber warum? Weil es eine plattformabhängige Lösung ist?
Qehgt
3
Nein ... es liegt daran, dass Sie große Datenmengen gleichzeitig schreiben müssen, um schnell sequentielle Dateien schreiben zu können. (Angenommen, 2-MiB-Chunks sind wahrscheinlich ein guter Ausgangspunkt.) Mit Speicherzuordnungsdateien können Sie die Granularität nicht steuern, sodass Sie dem ausgeliefert sind, was der Speichermanager für Sie vorab abruft / puffert. Im Allgemeinen habe ich noch nie gesehen, dass sie ReadFilefür den sequentiellen Zugriff so effektiv sind wie normales Lesen / Schreiben und dergleichen, obwohl sie für den wahlfreien Zugriff möglicherweise besser sind.
user541686
Speicherzugeordnete Dateien werden jedoch beispielsweise vom Betriebssystem für das Paging verwendet. Ich denke, es ist eine hochoptimierte (in Bezug auf die Geschwindigkeit) Art, Daten zu lesen / schreiben.
Qehgt
7
@Mysticial: Die Leute wissen "viele Dinge, die einfach falsch sind.
Ben Voigt
1
@qehgt: Wenn überhaupt, ist Paging für den Direktzugriff viel besser optimiert als für den sequentiellen Zugriff. Das Lesen von 1 Seite Daten ist viel langsamer als das Lesen von 1 Megabyte Daten in einem einzigen Vorgang.
user541686
3

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)

  • Gabel.
  • Verwenden Sie einen Thread, um [] Daten zu generieren
  • Verwenden Sie die andere, um Daten von einem [] auf die Festplatte zu schreiben
  • Sie benötigen zwei Arrays a1 [] und a2 [] und wechseln zwischen ihnen
  • Sie benötigen eine Art Synchronisation zwischen Ihren Threads (Semaphoren, Nachrichtenwarteschlange usw.)
  • Verwenden Sie ungepufferte Funktionen niedrigerer Ebene, wie die von Mehrdad erwähnte WriteFile- Funktion
verdoppelt
quelle
1

Wenn Sie schnell in Dateistreams schreiben möchten, können Sie den Lesepuffer für Streams vergrößern:

wfstream f;
const size_t nBufferSize = 16184;
wchar_t buffer[nBufferSize];
f.rdbuf()->pubsetbuf(buffer, nBufferSize);

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 SetFileValidDataoder xfsctlmit XFS_IOC_RESVSP64auf XFS-Systemen unterstützt.


quelle
0

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

makeFile("Text.txt",1024,8192000);

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

Ethaan
quelle