J'essaie d'écrire d'énormes quantités de données sur mon SSD (disque SSD). Et par énormes quantités, je veux dire 80 Go.
J'ai parcouru le Web pour trouver des solutions, mais le meilleur que j'ai trouvé est le suivant:
#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 optimisations complètes et exécuté sous Windows7, 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 vers 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?
c++
performance
optimization
file-io
io
Dominic Hofer
la source
la source
fwrite()
je pouvais obtenir environ 80% des vitesses d'écriture de pointe. C'est seulement avec queFILE_FLAG_NO_BUFFERING
j'ai pu obtenir la vitesse maximale.Réponses:
Cela a fait le travail (en 2012):
Je viens de chronométrer 8 Go en 36 secondes, ce qui représente environ 220 Mo / s et je pense que cela maximise mon SSD. Il convient également de noter que le code de la question utilise un cœur à 100%, alors que ce code utilise uniquement 2 à 5%.
Merci beaucoup à tous.
Mise à jour : 5 ans se sont écoulés, c'est 2017 maintenant. Les compilateurs, le matériel, les bibliothèques et mes exigences ont changé. C'est pourquoi j'ai apporté quelques modifications au code et fait de nouvelles mesures.
Tout d'abord, le code:
Ce code se compile avec Visual Studio 2017 et g ++ 7.2.0 (une nouvelle configuration requise). J'ai exécuté le code avec deux configurations:
Ce qui a donné les mesures suivantes (après avoir abandonné les valeurs de 1 Mo, car elles étaient des valeurs aberrantes évidentes): Les fois option1 et option3 ont maximisé mon SSD. Je ne m'attendais pas à voir cela, car l'option2 était le code le plus rapide sur mon ancienne machine à l'époque.
TL; DR : Mes mesures indiquent une utilisation
std::fstream
excessiveFILE
.la source
FILE*
est plus rapide que les flux. Je ne m'attendais pas à une telle différence car il "aurait dû" être lié aux E / S de toute façon.ios::sync_with_stdio(false);
fait-il une différence pour le code avec stream? Je suis simplement curieux de voir à quel point il y a une grande différence entre l'utilisation de cette ligne et non, mais je n'ai pas le disque assez rapide pour vérifier le cas du coin. Et s'il y a une réelle différence.Essayez les opérations suivantes, dans l'ordre:
Taille de tampon plus petite. Écrire ~ 2 Mio à la fois pourrait être un bon début. Sur mon dernier ordinateur portable, ~ 512 Ko était le point idéal, mais je n'ai pas encore testé sur mon SSD.
Remarque: j'ai remarqué que les très grands tampons ont tendance à diminuer les performances. J'ai déjà remarqué des pertes de vitesse avec l'utilisation de tampons de 16 Mio au lieu de 512 Kio.
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 comme
CreateFile
etWriteFile
. Cela évitera toute mise en mémoire tampon dans la bibliothèque standard.la source
FILE_FLAG_NO_BUFFERING
- dans lequel les plus gros tampons ont tendance à être meilleurs. Depuis, je pense queFILE_FLAG_NO_BUFFERING
c'est à peu près DMA.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:
Je vois le code s'exécuter en 63 secondes.
Ainsi, un taux de transfert de: 260M / s (mon SSD semble légèrement plus rapide que le vôtre).
Je n'obtiens aucune augmentation en passant au FICHIER * de std :: fstream.
Ainsi, le flux C ++ fonctionne 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 qui est construite sur le système d'exploitation. L'application ne peut faire aucune hypothèse (elle ne sait pas que les disques sont SSD) et utilise donc les mécanismes de fichiers de l'OS pour le transfert.
Alors que l'OS n'a pas besoin de faire d'hypothèses. Il peut indiquer les types de lecteurs impliqués et utiliser la technique optimale pour transférer les données. Dans ce cas, un transfert direct de mémoire à mémoire. Essayez d'écrire un programme qui copie 80G d'un emplacement en mémoire à un autre et voyez à quelle vitesse c'est.
Éditer
J'ai changé mon code pour utiliser les appels de niveau inférieur:
pas de mise en mémoire tampon.
Cela n'a fait aucune différence.
REMARQUE : Mon disque est un disque SSD si vous avez un disque normal, vous pouvez voir une différence entre les deux techniques ci-dessus. Mais comme je m'y attendais, la non mise en mémoire tampon et la mise en mémoire tampon (lors de l'écriture de gros morceaux supérieurs à la taille du tampon) ne font aucune différence.
Modifier 2:
Avez-vous essayé la méthode la plus rapide pour copier des fichiers en C ++
la source
FILE*
.La meilleure solution consiste à implémenter une écriture asynchrone avec double tampon.
Regardez la chronologie:
Le «F» représente le temps de remplissage du tampon et le «W» représente le temps d'écriture du tampon sur le disque. Donc, le problème de perdre du temps entre l'écriture de tampons dans un fichier. Cependant, en implémentant l'écriture sur un thread séparé, vous pouvez commencer à remplir immédiatement le tampon suivant comme ceci:
F - remplissage du premier tampon
f - remplissage du deuxième tampon
W - écriture du premier tampon dans le fichier
w - écriture du deuxième tampon dans le fichier
_ - attendez la fin de l'opération
Cette approche avec des échanges de tampons 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 l'écriture asynchrone à l'intérieur, donc pour l'utilisateur final, l'interface n'a que des fonctions d'écriture.
Et la taille du tampon doit être un multiple de la taille du cluster de disques. Sinon, vous obtiendrez de mauvaises performances en écrivant un seul tampon dans 2 clusters de disques adjacents.
Écriture du 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 (final buffer flush), qui devrait écrire sur le disque la dernière partie des données.
La gestion des erreurs.
Alors que le code commence à remplir le deuxième tampon et que le premier 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.
Supposons que l'interface d'un CSequentialStreamWriter a une fonction Write renvoie bool ou lève une exception, ayant ainsi une erreur sur un thread séparé, vous devez vous souvenir de cet état, donc la prochaine fois que vous appellerez Write ou Finilize sur le thread principal, la méthode retournera Faux ou lèvera une exception. Et peu importe à quel moment vous avez cessé de remplir un tampon, même si vous avez écrit des données à l'avance après l'échec - le fichier sera probablement corrompu et inutile.
la source
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 j'ai pu atteindrela source
Pourriez-vous utiliser à la
FILE*
place et mesurer les performances que vous avez gagnées? Quelques options sont à utiliserfwrite/write
au lieu defstream
:Si vous décidez d'utiliser
write
, essayez quelque chose de similaire:Je vous conseillerais également de vous renseigner
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 pour utiliser la carte mémoire. Je l'ai faitPython
cependant.la source
FILE*
équivalent simple du code d'origine utilisant le même tampon de 512 Mo obtient sa pleine vitesse. Votre mémoire tampon actuelle est trop petite.2
correspond à une erreur standard, mais il est toujours recommandé de l'utiliser à laSTDERR_FILENO
place de2
. Un autre problème important est qu'une erreur possible que vous pouvez obtenir est EINTR lorsque vous recevez un signal d'interruption, ce n'est pas une vraie erreur et vous devez simplement réessayer.Essayez d'utiliser les appels API open () / write () / close () et testez la taille du tampon de sortie. Je veux dire ne passez pas tout le tampon "plusieurs-plusieurs-octets" à la fois, faites quelques écritures (c.-à-d. TotalNumBytes / OutBufferSize). OutBufferSize peut être compris entre 4096 octets et 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.
Quoi qu'il en soit, std :: ofstream est un wrapper et il peut y avoir un blocage des opérations d'E / S. Gardez à l'esprit que la traversée de l'ensemble de la baie de N gigaoctets prend également un certain temps. Pendant que vous écrivez un petit tampon, il arrive dans le cache et fonctionne plus rapidement.
la source
fstream
s ne sont pas plus lents que les flux C, en soi, mais ils utilisent plus de CPU (surtout si la mise en mémoire tampon n'est pas correctement configurée). Lorsqu'un CPU sature, il 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 flux n'est pas défini (voir
streambuf::xsputn
). Donc , assurez - vous de mettre un tampon de flux (> 0) .Je peux obtenir une vitesse d'écriture de 1500 Mo / s (la pleine vitesse de mon SSD M.2) en
fstream
utilisant ce code: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 (
fstream
utilisé 8 fois plus de processeur ). On peut donc imaginer que si j'avais un disque plus rapide, l'fstream
écriture ralentirait plus tôt que lastdio
version.la source
Si vous copiez quelque chose du disque A vers le disque B dans l'explorateur, Windows utilise DMA. Cela signifie que pour la plupart du processus de copie, le CPU ne fera rien d'autre que de dire au contrôleur de disque où placer et obtenir les données, éliminant ainsi une étape entière de la chaîne, et qui n'est 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 de CPU. Je veux vous indiquer la partie "Quelques calculs pour remplir une []". Je pense que c'est essentiel. Vous générez un [], puis vous copiez d'un [] vers un tampon de sortie (c'est ce que fstream :: write fait), puis vous générez à nouveau, etc.
Que faire? Multithreading! (J'espère que vous avez un processeur multicœur)
la source
Essayez d'utiliser des fichiers mappés en mémoire.
la source
ReadFile
et autres pour un accès séquentiel, bien que pour un accès aléatoire, ils puissent être meilleurs.Si vous voulez écrire rapidement dans des flux de fichiers, vous pouvez agrandir le tampon de lecture:
De plus, lors de l'écriture de nombreuses données dans des fichiers, il est parfois plus rapide d' étendre logiquement la taille du fichier plutôt que physiquement, car en étendant logiquement un fichier, le système de fichiers ne met pas à zéro le nouvel espace avant d'y écrire. Il est également judicieux d'étendre logiquement le fichier plus que ce dont vous avez réellement besoin pour éviter de nombreuses extensions de fichier. L'extension de fichier logique est prise en charge sous Windows en appelant
SetFileValidData
ouxfsctl
avecXFS_IOC_RESVSP64
sur les systèmes XFS.la source
im compiler mon programme dans gcc sous GNU / Linux et mingw dans win 7 et win xp et a bien fonctionné
vous pouvez utiliser mon programme et pour créer un fichier de 80 Go, changez simplement la ligne 33 en
lorsque vous quittez le programme, le fichier sera détruit, puis vérifiez le fichier lorsqu'il est en cours d'exécution
d'avoir le programme que vous voulez changer simplement 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
la source