Ecrire un fichier binary en C ++ très rapidement

J’essaie d’écrire d’énormes quantités de données sur mon SSD (lecteur à état solide). Et par énormes quantités, je veux dire 80 Go.

J’ai parcouru le Web pour trouver des solutions, mais le mieux que j’ai trouvé est le suivant:

#include  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(); } 

Compilé avec Visual Studio 2010 et optimisations complètes et exécuté sous Windows 7, ce programme atteint environ 20 Mo / s. Ce qui me dérange vraiment, c’est que Windows peut copier des fichiers d’un autre SSD sur ce SSD entre 150 Mo / s et 200 Mo / s. Donc au moins 7 fois plus vite. C’est pourquoi je pense que je devrais pouvoir aller plus vite.

Des idées pour accélérer mon écriture?

Edit: Maintenant, il comstack.

Cela a fait le travail:

 #include  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; } 

Je viens de chronométrer 8 Go en 36 secondes, ce qui est environ 220 Mo / s et je pense que cela maximise mon SSD. Il convient également de noter que le code utilisé dans la question utilisait un seul kernel à 100%, alors que ce code n’utilisait que 2 à 5%.

Merci beaucoup à tous.

Mise à jour : 5 ans ont passé. Les compilateurs, le matériel, les bibliothèques et mes besoins ont changé. C'est pourquoi j'ai apporté quelques modifications au code et fait des mesures.

Tout d'abord le code:

 #include  #include  #include  #include  #include  #include  #include  #include  #include  std::vector GenerateData(std::size_t bytes) { assert(bytes % sizeof(uint64_t) == 0); std::vector 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 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(endTime - startTime).count(); } long long option_2(std::size_t bytes) { std::vector 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(endTime - startTime).count(); } long long option_3(std::size_t bytes) { std::vector 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(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; } 

Maintenant, le code comstack avec Visual Studio 2017 et g ++ 7.2.0 (qui est maintenant l'un de mes besoins). Je laisse le code s'exécuter avec deux configurations:

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

Ce qui a donné les mesures suivantes (après avoir abandonné les valeurs pour 1 Mo, car elles étaient des valeurs aberrantes évidentes): entrer la description de l'image ici entrer la description de l'image ici Les deux fois option1 et option3 maximisent mon SSD. Je ne m'attendais pas à ce que cela se voit, car option2 était le code le plus rapide de ma machine à l'époque.

TL; DR : Mes mesures indiquent qu'il faut utiliser std::fstream sur FILE .

Essayez ce qui suit, dans l’ordre:

  • Plus petite taille de tampon. Écrire ~ 2 Mio à la fois pourrait être un bon début. Sur mon dernier ordinateur portable, ~ 512 KiB était le bon point, mais je n’ai pas encore testé sur mon SSD.

    Note: J’ai remarqué que les tampons de très grande taille ont tendance à diminuer les performances. J’ai remarqué des pertes de vitesse en utilisant des tampons de 16 Mo au lieu de 512 Ko auparavant.

  • Utilisez _open (ou _topen si vous voulez que Windows soit correct) pour ouvrir le fichier, puis utilisez _write . Cela évitera probablement beaucoup de mise en mémoire tampon, mais ce n’est pas certain.

  • Utilisation de fonctions spécifiques à Windows telles que CreateFile et WriteFile . Cela évitera toute mise en mémoire tampon dans la bibliothèque standard.

Je ne vois aucune différence entre std :: stream / FILE / device. Entre mise en mémoire tampon et non mise en mémoire tampon.

Notez également:

  • Les disques SSD ont tendance à ralentir (taux de transfert plus bas) à mesure qu’ils se remplissent.
  • Les disques SSD ont tendance à ralentir (taux de transfert inférieurs) à mesure qu’ils vieillissent (à cause des bits non fonctionnels).

Je vois le code courir dans 63 secondes.
Ainsi, un taux de transfert de: 260 M / s (mon SSD semble légèrement plus rapide que le vôtre).

 64 * 1024 * 1024 * 8 /*sizeof(unsigned long long) */ * 32 /*Chunks*/ = 16G = 16G/63 = 260M/s 

Je ne reçois aucune augmentation en passant à FILE * à partir de std :: fstream.

 #include  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); } 

Le stream C ++ fonctionne donc aussi vite que la bibliothèque sous-jacente le permet.

Mais je pense qu’il est injuste de comparer le système d’exploitation à une application intégrée au système d’exploitation. L'application ne peut faire aucune hypothèse (elle ne connaît pas les disques SSD) et utilise donc les mécanismes de fichiers du système d'exploitation pour le transfert.

Alors que le système d'exploitation n'a pas besoin de faire de suppositions. Il peut indiquer les types de lecteurs impliqués et utiliser la technique optimale pour transférer les données. Dans ce cas, une mémoire directe au transfert de mémoire. Essayez d'écrire un programme qui copie 80G d'un emplacement en mémoire vers un autre et voyez à quelle vitesse.

modifier

J'ai changé mon code pour utiliser les appels de niveau inférieur:
c'est-à-dire pas de mise en mémoire tampon.

 #include  #include  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); } 

Cela n'a fait aucune différence.

REMARQUE : Mon lecteur est un lecteur SSD si vous avez un lecteur normal, vous pouvez voir une différence entre les deux techniques ci-dessus. Mais comme je m'y attendais, la mise en mémoire tampon et la mise en mémoire tampon (lors de l'écriture de gros blocs supérieurs à la taille de la mémoire tampon) ne font aucune différence.

Edit 2:

Avez-vous essayé la méthode la plus rapide de copie de fichiers en C ++

 int main() { std::ifstream input("input"); std::ofstream output("ouptut"); output << input.rdbuf(); } 

La meilleure solution consiste à implémenter une écriture asynchrone avec un double tampon.

Regardez la chronologie:

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

Le «F» représente le temps nécessaire au remplissage du tampon et «W» représente le temps nécessaire à l’écriture du tampon sur le disque. Donc, le problème en perdant du temps entre écrire des tampons à classer. Cependant, en implémentant l’écriture sur un thread séparé, vous pouvez commencer à remplir le tampon suivant comme ceci:

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

F – remplissage 1er tampon
f – remplissage 2ème tampon
W – écriture de la 1ère mémoire tampon dans un fichier
w – écrire la 2ème mémoire tampon dans le fichier
_ – attendez que l’opération soit terminée

Cette approche avec swap de tampon est très utile lorsque le remplissage d’un tampon nécessite un calcul plus complexe (donc plus de temps). J’implémente toujours une classe CSequentialStreamWriter qui cache une écriture asynchrone à l’intérieur, donc pour l’utilisateur final, l’interface n’a qu’une fonction d’écriture.

Et la taille de la mémoire tampon doit être un multiple de la taille du cluster de disques. Sinon, vous vous retrouverez avec de mauvaises performances en écrivant un seul tampon sur 2 clusters de disques adjacents.

Ecrire le dernier tampon.
Lorsque vous appelez la fonction Write pour la dernière fois, vous devez vous assurer que le tampon en cours de remplissage doit également être écrit sur le disque. Ainsi, CSequentialStreamWriter devrait avoir une méthode distincte, disons Finalize (vidage final du tampon), qui devrait écrire sur le disque la dernière partie des données.

La gestion des erreurs.
Alors que le code commence à remplir le 2ème tampon et que le 1er est écrit sur un thread séparé, mais que l’écriture échoue pour une raison quelconque, le thread principal doit être conscient de cet échec.

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

Supposons que l’interface d’un CSequentialStreamWriter a la fonction Write qui retourne bool ou jette une exception, ayant ainsi une erreur sur un thread séparé, vous devez vous souvenir de cet état, donc la prochaine fois que vous appelez Write ou Finilize sur le thread principal Faux ou va lancer une exception. Et peu importe à quel point vous avez cessé de remplir un tampon, même si vous aviez écrit des données après l’échec, le fichier serait probablement corrompu et inutile.

Je suggère d’essayer le mappage de fichiers . J’ai utilisé mmap dans le passé, dans un environnement UNIX, et j’ai été impressionné par les hautes performances que je pouvais atteindre

Pourriez-vous utiliser FILE* place et mesurer les performances que vous avez obtenues? Quelques options sont d’utiliser fwrite/write au lieu de fstream :

 #include  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; } 

Si vous décidez d’utiliser l’ write , essayez quelque chose de similaire:

 #include  #include  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; } 

Je vous conseille également de regarder memory map . C'est peut-être votre réponse. Une fois, je devais traiter un fichier de 20 Go dans un autre fichier pour le stocker dans la firebase database et le fichier ne s’ouvrait même pas. Donc, la solution consiste à utiliser la carte mémoire. Je l'ai fait en Python si.

Essayez d’utiliser les appels API open () / write () / close () et testez la taille du tampon de sortie. Je veux dire ne pas passer tout le tampon “many-many-bytes” en même temps, faire quelques écritures (ie, TotalNumBytes / OutBufferSize). OutBufferSize peut être de 4096 octets en mégaoctets.

Un autre essai – utilisez WinAPI OpenFile / CreateFile et utilisez cet article MSDN pour désactiver la mise en mémoire tampon (FILE_FLAG_NO_BUFFERING). Et cet article MSDN sur WriteFile () montre comment obtenir la taille de bloc pour que le lecteur connaisse la taille de tampon optimale.

De toute façon, std :: ofstream est un wrapper et il peut y avoir des blocages sur les opérations d’E / S. Gardez à l’esprit que le fait de parcourir la totalité du réseau N-gigaoctets prend également du temps. Pendant que vous écrivez un petit tampon, il arrive dans le cache et fonctionne plus rapidement.

Essayez d’utiliser des fichiers mappés en mémoire.

Si vous copiez quelque chose du disque A sur le disque B dans l’explorateur, Windows utilise DMA. Cela signifie que pour la majeure partie du processus de copie, le processeur ne fera rien d’autre que de dire au contrôleur de disque où placer et extraire les données, éliminant ainsi une étape entière de la chaîne, et pas du tout optimisée pour déplacer de grandes quantités. de données – et je veux dire le matériel.

Ce que vous faites implique beaucoup le CPU. Je veux vous diriger vers la partie “Quelques calculs pour remplir un []”. Ce que je pense est essentiel. Vous générez un [], puis vous copiez d’un [] vers un tampon de sortie (c’est ce que fait fstream :: write), puis vous générez à nouveau, etc.

Que faire? Multithreading! (J’espère que vous avez un processeur multi-core)

  • fourchette.
  • Utilisez un thread pour générer des données []
  • Utilisez l’autre pour écrire des données d’un [] sur le disque
  • Vous aurez besoin de deux tableaux a1 [] et a2 [] et basculez entre eux
  • Vous aurez besoin d’une sorte de synchronisation entre vos threads (sémaphores, queue de messages, etc.)
  • Utilisez des fonctions de niveau inférieur, sans tampon, comme la fonction WriteFile mentionnée par Mehrdad

fstream s ne sont pas plus lents que les stream C, mais ils utilisent plus de CPU (surtout si la mise en mémoire tampon n’est pas correctement configurée). Lorsqu’une CPU sature, cela limite le taux d’E / S.

Au moins l’implémentation MSVC 2015 copie 1 caractère à la fois dans le tampon de sortie lorsqu’un tampon de stream n’est pas défini (voir streambuf::xsputn ). Veillez donc à définir un tampon de stream (> 0) .

Je peux obtenir une vitesse d’écriture de 1500 Mo / s (la vitesse maximale de mon SSD M.2) avec fstream utilisant ce code:

 #include  #include  #include  #include  #include  #ifdef __linux__ #include  #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 data(new char[sz]); unique_ptr 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(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(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"; } 

J'ai essayé ce code sur d'autres plates-formes (Ubuntu, FreeBSD) et je n'ai remarqué aucune différence de taux d'E / S, mais une différence d' utilisation du processeur d'environ 8: 1 (le fstream utilisé était 8 fois plus fstream ). On peut donc imaginer, si j'avais un disque plus rapide, que l'écriture fstream ralentirait plus tôt que la version stdio .

Si vous voulez écrire rapidement dans les stream de fichiers, vous pouvez augmenter la taille du stream de lecture:

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

En outre, lorsque vous écrivez beaucoup de données dans des fichiers, il est parfois plus rapide d’étendre logiquement la taille du fichier plutôt que physiquement, car le système de fichiers ne prolonge pas le nouvel espace avant d’écrire. Il est également judicieux d’étendre logiquement le fichier plus que nécessaire pour éviter de nombreuses extensions de fichiers. L’extension de fichier logique est prise en charge sous Windows en appelant SetFileValidData ou xfsctl avec XFS_IOC_RESVSP64 sur les systèmes XFS.

Im comstackr mon programme en gcc dans GNU / Linux et mingw en win 7 et gagner xp et a bien fonctionné

vous pouvez utiliser mon programme et créer un fichier de 80 Go simplement changer la ligne 33 à

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

Lorsque vous quittez le programme, le fichier sera détruit, puis vérifiez le fichier lorsqu’il est en cours d’exécution

avoir le programme que vous voulez juste changer le programme

Le premier est le programme Windows et le second est pour GNU / Linux

http://mustafajf.persiangig.com/Projects/File/WinFile.cpp

http://mustafajf.persiangig.com/Projects/File/File.cpp