J'essaie d'écrire d'énormes quantités de données sur mon SSD (Solid State Drive). 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 la suivante:
#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();
}
Compilé avec Visual Studio 2010 et des optimisations complètes et exécuté sous Windows 7, ce programme a une capacité maximale de 20 Mo/s. Ce qui me dérange vraiment, c’est que Windows peut copier des fichiers d’un autre disque SSD vers ce disque 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?
Cela a fait le travail:
#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;
}
Je viens de chronométrer 8 Go en 36s, ce qui est d'environ 220 Mo/s et je pense que maximise mon SSD. Il convient également de noter que le code de la question utilisait un noyau à 100%, alors que ce code n'en utilise que 2 à 5%.
Merci beaucoup à tous.
Mise à jour : 5 ans se sont écoulés. Les compilateurs, le matériel, les bibliothèques et mes besoins ont changé. C'est pourquoi j'ai apporté des modifications au code et des mesures.
D'abord le 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;
}
Maintenant, le code est compilé avec Visual Studio 2017 et g ++ 7.2.0 (qui est maintenant l'une de mes exigences). J'ai laissé le code s'exécuter avec deux configurations:
Ce qui a donné les mesures suivantes (après avoir abandonné les valeurs pour 1 Mo, car il s’agissait de valeurs aberrantes évidentes): Les deux fois option1 et option3 max sur mon SSD. Je ne m'attendais pas à ce que cela se voit, car option2 était à l'époque le code le plus rapide sur ma machine.
TL; DR : Mes mesures indiquent d’utiliser std::fstream
sur FILE
.
Essayez ce qui suit, dans l'ordre:
Petite taille de la mémoire tampon. Écrire ~ 2 Mio à la fois pourrait être un bon début. Sur mon dernier ordinateur portable, ~ 512 KiB était la solution idéale, mais je n'ai pas encore testé mon SSD.
Note: J'ai remarqué que les très grands tampons ont tendance à diminuer les performances. J'ai déjà constaté des pertes de vitesse avec l'utilisation de tampons de 16 Mo au lieu de 512 Ko auparavant.
Utilisez _open
(ou _topen
si vous voulez utiliser Windows correctement) pour ouvrir le fichier, puis utilisez _write
. Ceci é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 aussi:
Je vois le code exécuté en 63 secondes.
Ainsi, un taux de transfert de: 260 M/s (mon disque SSD est 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 n'obtiens aucune augmentation en passant à FILE * à partir de std :: fstream.
#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);
}
Les flux C++ fonctionnent 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 construite sur le système d'exploitation. L'application ne peut faire aucune hypothèse (elle ne sait pas que les disques sont des disques SSD) et utilise donc les mécanismes de fichiers du système d'exploitation pour le transfert.
Bien que le système d'exploitation n'a pas besoin de faire des hypothèses. Il peut indiquer les types de disques impliqués et utiliser la technique optimale pour transférer les données. Dans ce cas, un transfert direct de mémoire en mémoire. Essayez d’écrire un programme qui copie 80G d’un emplacement en mémoire à un autre et voyez à quelle vitesse.
J'ai changé mon code pour utiliser les appels de niveau inférieur:
C'est-à-dire pas de tampon.
#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);
}
Cela n'a fait aucune différence.
NOTE: Mon lecteur est un lecteur SSD. Si vous avez un lecteur normal, vous constaterez peut-être une différence entre les deux techniques. au dessus de. 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 morceaux supérieurs à la taille de la mémoire tampon) ne font aucune différence.
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 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 le "W" au temps nécessaire à l'écriture du tampon sur le disque. Donc, le problème de perdre du temps entre l’écriture des tampons dans le fichier. Cependant, en implémentant l'écriture sur un thread séparé, vous pouvez commencer à remplir immédiatement le tampon suivant comme ceci:
------------------------------------------------> (main thread, fills buffers)
FF|ff______|FF______|ff______|________|
------------------------------------------------> (writer thread)
|WWWWWWWW|wwwwwwww|WWWWWWWW|wwwwwwww|
F - remplissage du 1er tampon
f - remplissage du second tampon
W - écriture du premier tampon dans un fichier
w - écriture du deuxième tampon dans un fichier
_ - attend que l'opération soit terminée
Cette approche avec permutation de mémoire tampon est très utile lorsque le remplissage d’une mémoire tampon nécessite des calculs plus complexes (et donc plus de temps). J'implémente toujours une classe CSequentialStreamWriter qui cache l'écriture asynchrone à l'intérieur. Ainsi, pour l'utilisateur final, l'interface n'a que des fonctions d'écriture.
Et la taille de la mémoire tampon doit être un multiple de la taille du cluster de disques. Sinon, vous obtiendrez de mauvaises performances en écrivant un seul tampon sur 2 clusters de disques adjacents.
Ecrire le dernier tampon.
Lorsque vous appelez la fonction Écriture pour la dernière fois, vous devez vous assurer que le tampon actuel en cours de remplissage est également écrit sur le disque. Ainsi, CSequentialStreamWriter devrait avoir une méthode distincte, par exemple Finalize (flush final), qui devrait écrire sur le disque la dernière partie des données.
La gestion des erreurs.
Pendant que le code commence à remplir le second tampon et que le premier est en cours d'écriture sur un thread distinct, 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 ait une fonction Write qui retourne un bool ou lève une exception. Ainsi, en cas d'erreur sur un thread séparé, vous devez vous souvenir de cet état. Ainsi, la prochaine fois que vous appelez Write ou Finilize sur le thread principal, la méthode retournera Faux ou jettera une exception. Et peu importe à quel moment vous avez cessé de remplir une mémoire tampon, même si vous avez écrit des données à l’avenir après l’échec - le fichier serait probablement corrompu et inutile.
Je suggérerais d'essayer mappage de fichier . J'ai déjà utilisé mmap
dans un environnement UNIX et j'ai été impressionné par les performances élevées que je pouvais atteindre.
Pourriez-vous utiliser FILE*
à la place et mesurer la performance que vous avez obtenue? Quelques options consistent à utiliser fwrite/write
au lieu de 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;
}
Si vous décidez d'utiliser write
, essayez quelque chose de similaire:
#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;
}
Je vous conseillerais également de vous pencher sur memory map
. C'est peut-être votre réponse. Une fois, j'ai dû traiter un fichier de 20 Go dans un autre pour le stocker dans la base de données, et le fichier ne s'ouvrait même pas. Donc, la solution consiste à utiliser la carte mémoire. Je l'ai fait dans Python
bien que.
Essayez d’utiliser les appels d’API open ()/write ()/close () et expérimentez la taille du tampon de sortie. Je veux dire, ne passez pas tout le tampon "plusieurs-plusieurs-octets" à la fois, faites quelques écritures (c'est-à-dire, TotalNumBytes/OutBufferSize). OutBufferSize peut être compris entre 4096 octets et mégaoctets.
Une autre tentative - 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 optimale de la mémoire tampon.
Quoi qu'il en soit, std :: ofstream est un wrapper et il pourrait y avoir un blocage sur les opérations d'E/S. N'oubliez pas que parcourir l'ensemble du tableau de N-gigaoctets prend également un certain temps. Lorsque vous écrivez un petit tampon, celui-ci arrive dans le cache et fonctionne plus rapidement.
Si vous copiez quelque chose du disque A sur le disque B dans l'Explorateur, Windows utilise DMA. Cela signifie que, pour l'essentiel du processus de copie, le processeur ne fera en principe rien d'autre que d'indiquer au contrôleur de disque où placer les données et en extraire les données, ce qui élimine toute une étape de la chaîne, qui n'est pas du tout optimisée pour le déplacement de grandes quantités. des données - et je veux dire le matériel.
Ce que vous implique beaucoup le processeur. Je tiens à vous indiquer la partie "Quelques calculs pour remplir un []". Ce que je pense est essentiel. Vous générez un [], vous copiez un [] dans un tampon de sortie (c'est ce que fstream :: write fait), vous générez à nouveau, etc.
Que faire? Multithreading! (J'espère que vous avez un processeur multi-core)
fstream
s ne sont pas plus lents que les flux C, mais ils utilisent plus de CPU (surtout si la mise en mémoire tampon n'est pas correctement configurée). Lorsqu'un processeur sature, il limite le taux d'E/S.
Au moins l'implémentation de MSVC 2015 copie 1 caractère à la fois dans le tampon de sortie lorsqu'un tampon de flux n'est pas défini (voir streambuf::xsputn
). Donc, assurez-vous de définir un tampon de flux (> 0).
Je peux obtenir une vitesse d'écriture de 1500 Mo/s (la vitesse maximale de mon SSD M.2) avec fstream
en utilisant ce code:
#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";
}
J'ai essayé ce code sur d'autres plateformes (Ubuntu, FreeBSD) et n'ai constaté aucune différence de taux d'E/S, mais une utilisation du processeur différence d'environ 8: 1 (fstream
utilisée 8 fois plus de CPU ). On peut donc imaginer que si j’avais un disque plus rapide, l’écriture fstream
ralentirait plus tôt que la version stdio
.
Essayez d’utiliser des fichiers mappés en mémoire.
Si vous voulez écrire rapidement dans des flux de fichiers, vous pouvez agrandir le tampon de lecture:
wfstream f;
const size_t nBufferSize = 16184;
wchar_t buffer[nBufferSize];
f.rdbuf()->pubsetbuf(buffer, nBufferSize);
De plus, lors de l'écriture de nombreuses données dans des fichiers, il est parfois plus rapide de logiquement étendre la taille du fichier plutôt que physiquement, car lors de l'extension logique d'un fichier, le système de fichiers ne met pas à zéro le nouvel espace disponible avant l'écriture. à cela. Il est également judicieux de prolonger 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 compiler mon programme en gcc dans GNU/Linux et mingw dans win 7 et gagner xp et a bien fonctionné
vous pouvez utiliser mon programme et créer un fichier de 80 Go il suffit de changer la ligne 33
makeFile("Text.txt",1024,8192000);
quand quitter le programme le fichier sera détruit puis vérifier le fichier quand il est en cours d'exécution
d'avoir le programme que vous voulez juste changer de programme
le premier est le programme windows et le second est pour GNU/Linux