Je recherche un bon moyen de copier un fichier (binaire ou texte). J'ai écrit plusieurs échantillons, tout le monde travaille. Mais je veux entendre l'opinion de programmeurs chevronnés.
Il me manque de bons exemples et recherche un moyen qui fonctionne avec C ++.
ANSI-C-WAY
#include <iostream>
#include <cstdio> // fopen, fclose, fread, fwrite, BUFSIZ
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
// BUFSIZE default is 8192 bytes
// BUFSIZE of 1 means one chareter at time
// good values should fit to blocksize, like 1024 or 4096
// higher values reduce number of system calls
// size_t BUFFER_SIZE = 4096;
char buf[BUFSIZ];
size_t size;
FILE* source = fopen("from.ogv", "rb");
FILE* dest = fopen("to.ogv", "wb");
// clean and more secure
// feof(FILE* stream) returns non-zero if the end of file indicator for stream is set
while (size = fread(buf, 1, BUFSIZ, source)) {
fwrite(buf, 1, size, dest);
}
fclose(source);
fclose(dest);
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
POSIX-WAY (K&R l'utilise dans "Le langage de programmation C", plus bas niveau)
#include <iostream>
#include <fcntl.h> // open
#include <unistd.h> // read, write, close
#include <cstdio> // BUFSIZ
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
// BUFSIZE defaults to 8192
// BUFSIZE of 1 means one chareter at time
// good values should fit to blocksize, like 1024 or 4096
// higher values reduce number of system calls
// size_t BUFFER_SIZE = 4096;
char buf[BUFSIZ];
size_t size;
int source = open("from.ogv", O_RDONLY, 0);
int dest = open("to.ogv", O_WRONLY | O_CREAT /*| O_TRUNC/**/, 0644);
while ((size = read(source, buf, BUFSIZ)) > 0) {
write(dest, buf, size);
}
close(source);
close(dest);
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
KISS-C ++ - Streambuffer-WAY
#include <iostream>
#include <fstream>
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
ifstream source("from.ogv", ios::binary);
ofstream dest("to.ogv", ios::binary);
dest << source.rdbuf();
source.close();
dest.close();
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
COPIE-ALGORITHME-C ++ - VOIE
#include <iostream>
#include <fstream>
#include <ctime>
#include <algorithm>
#include <iterator>
using namespace std;
int main() {
clock_t start, end;
start = clock();
ifstream source("from.ogv", ios::binary);
ofstream dest("to.ogv", ios::binary);
istreambuf_iterator<char> begin_source(source);
istreambuf_iterator<char> end_source;
ostreambuf_iterator<char> begin_dest(dest);
copy(begin_source, end_source, begin_dest);
source.close();
dest.close();
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
OWN-BUFFER-C ++ - WAY
#include <iostream>
#include <fstream>
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
ifstream source("from.ogv", ios::binary);
ofstream dest("to.ogv", ios::binary);
// file size
source.seekg(0, ios::end);
ifstream::pos_type size = source.tellg();
source.seekg(0);
// allocate memory for buffer
char* buffer = new char[size];
// copy file
source.read(buffer, size);
dest.write(buffer, size);
// clean up
delete[] buffer;
source.close();
dest.close();
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
LINUX-WAY // nécessite un noyau> = 2.6.33
#include <iostream>
#include <sys/sendfile.h> // sendfile
#include <fcntl.h> // open
#include <unistd.h> // close
#include <sys/stat.h> // fstat
#include <sys/types.h> // fstat
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
int source = open("from.ogv", O_RDONLY, 0);
int dest = open("to.ogv", O_WRONLY | O_CREAT /*| O_TRUNC/**/, 0644);
// struct required, rationale: function stat() exists also
struct stat stat_source;
fstat(source, &stat_source);
sendfile(dest, source, 0, stat_source.st_size);
close(source);
close(dest);
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
Environnement
- GNU / LINUX (Archlinux)
- Noyau 3.3
- GLIBC-2.15, LIBSTDC ++ 4.7 (GCC-LIBS), GCC 4.7, Coreutils 8.16
- Utilisation de RUNLEVEL 3 (multi-utilisateur, réseau, terminal, sans interface graphique)
- INTEL SSD-Postville 80 Go, rempli jusqu'à 50%
- Copier un fichier vidéo OGG de 270 Mo
Étapes à reproduire
1. $ rm from.ogg
2. $ reboot # kernel and filesystem buffers are in regular
3. $ (time ./program) &>> report.txt # executes program, redirects output of program and append to file
4. $ sha256sum *.ogv # checksum
5. $ rm to.ogg # remove copy, but no sync, kernel and fileystem buffers are used
6. $ (time ./program) &>> report.txt # executes program, redirects output of program and append to file
Résultats (CPU TIME utilisé)
Program Description UNBUFFERED|BUFFERED
ANSI C (fread/frwite) 490,000|260,000
POSIX (K&R, read/write) 450,000|230,000
FSTREAM (KISS, Streambuffer) 500,000|270,000
FSTREAM (Algorithm, copy) 500,000|270,000
FSTREAM (OWN-BUFFER) 500,000|340,000
SENDFILE (native LINUX, sendfile) 410,000|200,000
La taille du fichier ne change pas.
sha256sum imprime les mêmes résultats.
Le fichier vidéo est toujours lisible.
Des questions
- Quelle méthode préférez-vous?
- Connaissez-vous de meilleures solutions?
- Voyez-vous des erreurs dans mon code?
Connaissez-vous une raison pour éviter une solution?
FSTREAM (KISS, Streambuffer)
J'aime vraiment celui-ci, car il est vraiment court et simple. Pour l'instant, je sais que l'opérateur << est surchargé pour rdbuf () et ne convertit rien. Correct?
Merci
Mise à jour 1
J'ai changé la source dans tous les échantillons de cette façon, que l'ouverture et la fermeture des descripteurs de fichiers soient incluses dans la mesure de l' horloge () . Il n'y a aucun autre changement significatif dans le code source. Le résultat n'a pas changé! J'ai également utilisé du temps pour revérifier mes résultats.
Exemple de mise à jour 2
ANSI C modifié: la condition de la boucle while n'appelle plus feof () à la place, j'ai déplacé fread () dans la condition. Il semble que le code s'exécute maintenant 10 000 horloges plus rapidement.
La mesure a changé: les anciens résultats ont toujours été mis en mémoire tampon, car j'ai répété plusieurs fois l'ancienne ligne de commande rm to.ogv && sync && time ./program pour chaque programme. Maintenant, je redémarre le système pour chaque programme. Les résultats sans tampon sont nouveaux et ne montrent aucune surprise. Les résultats sans tampon n'ont pas vraiment changé.
Si je ne supprime pas l'ancienne copie, les programmes réagissent différemment. L'écrasement d'un fichier existant tamponné est plus rapide avec POSIX et SENDFILE, tous les autres programmes sont plus lents. Peut-être que les options tronquer ou créer ont un impact sur ce comportement. Mais écraser des fichiers existants avec la même copie n'est pas un cas d'utilisation réel.
La copie avec cp prend 0,44 seconde sans tampon et 0,30 seconde avec tampon. Donc, cp est un peu plus lent que l'exemple POSIX. Ça me va bien.
Peut-être que j'ajoute également des échantillons et les résultats de mmap () et copy_file()
de boost :: filesystem.
Mise à jour 3
J'ai également mis cela sur une page de blog et je l'ai étendu un peu. Y compris splice () , qui est une fonction de bas niveau du noyau Linux. Peut-être que d'autres échantillons avec Java suivront.
http://www.ttyhoney.com/blog/?page_id=69
fstream
est certainement une bonne option pour les opérations de fichiers.#include <copyfile.h> copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);
Réponses:
Copiez un fichier de manière saine:
C'est tellement simple et intuitif à lire qu'il vaut le coût supplémentaire. Si nous le faisions beaucoup, mieux vaut se rabattre sur les appels du système d'exploitation au système de fichiers. Je suis sûr qu'il
boost
a une méthode de copie de fichier dans sa classe de système de fichiers.Il existe une méthode C pour interagir avec le système de fichiers:
la source
copyfile
n'est pas portable; Je pense que c'est spécifique à Mac OS X. Il n'existe certainement pas sous Linux.boost::filesystem::copy_file
est probablement le moyen le plus portable de copier un fichier via le système de fichiers natif.Avec C ++ 17, la manière standard de copier un fichier sera d'inclure l'en-
<filesystem>
tête et d'utiliser:Le premier formulaire est équivalent au second avec
copy_options::none
utilisé comme options (voir aussicopy_file
).La
filesystem
bibliothèque a été initialement développéeboost.filesystem
et finalement fusionnée en ISO C ++ à partir de C ++ 17.la source
bool copy_file( const std::filesystem::path& from, const std::filesystem::path& to, std::filesystem::copy_options options = std::filesystem::copy_options::none);
?Trop!
Le tampon de manière "ANSI C" est redondant, car a
FILE
est déjà tamponné. (La taille de ce tampon interne est ceBUFSIZ
qui définit réellement.)Le "OWN-BUFFER-C ++ - WAY" sera lent au fur et à mesure
fstream
, ce qui fait beaucoup de répartition virtuelle et maintient à nouveau des tampons internes ou chaque objet de flux. (Le "COPY-ALGORITHM-C ++ - WAY" ne souffre pas de cela, car lastreambuf_iterator
classe contourne la couche de flux.)Je préfère le "COPY-ALGORITHM-C ++ - WAY", mais sans construire un
fstream
, il suffit de créer desstd::filebuf
instances nues quand aucun formatage réel n'est nécessaire.Pour les performances brutes, vous ne pouvez pas battre les descripteurs de fichiers POSIX. C'est moche mais portable et rapide sur n'importe quelle plateforme.
La méthode Linux semble être incroyablement rapide - peut-être que le système d'exploitation a laissé la fonction revenir avant la fin des E / S? En tout cas, ce n'est pas assez portable pour de nombreuses applications.
EDIT : Ah, "Linux natif" peut améliorer les performances en entrelaçant les lectures et les écritures avec des E / S asynchrones. Laisser les commandes s'empiler peut aider le pilote de disque à décider du meilleur moment. Vous pouvez essayer Boost Asio ou pthreads pour comparaison. Quant à «ne peut pas battre les descripteurs de fichiers POSIX»… c'est vrai si vous faites quoi que ce soit avec les données, pas seulement en les copiant aveuglément.
la source
sendfile()
copie les données entre un descripteur de fichier et un autre. Parce que cette copie est effectuée dans le noyau,sendfile()
est plus efficace que la combinaison deread(2)
etwrite(2)
, ce qui nécessiterait le transfert de données vers et depuis l'espace utilisateur.": kernel.org/doc/man-pages /online/pages/man2/sendfile.2.htmlfilebuf
objets bruts ?Je veux faire de la très importante noter que la méthode LINUX utilisant sendfile () a un problème majeur en ce qu'elle ne peut pas copier des fichiers de plus de 2 Go! Je l'avais implémenté suite à cette question et rencontrais des problèmes parce que je l'utilisais pour copier des fichiers HDF5 de plusieurs Go.
http://man7.org/linux/man-pages/man2/sendfile.2.html
la source
off64_t
permet d'utiliser une boucle pour copier des fichiers volumineux comme indiqué dans une réponse à la question liée.Qt a une méthode pour copier des fichiers:
Notez que pour l'utiliser, vous devez installer Qt (instructions ici ) et l'inclure dans votre projet (si vous utilisez Windows et que vous n'êtes pas administrateur, vous pouvez télécharger Qt ici à la place). Voir également cette réponse .
la source
QFile::copy
est ridiculement lent en raison de sa mise en mémoire tampon 4k .Qt
. J'utilise5.9.2
et la vitesse est comparable à l'implémentation native. Btw. en jetant un œil au code source, Qt semble en fait appeler l'implémentation native.Pour ceux qui aiment le boost:
Notez que boost :: filesystem :: path est également disponible en tant que wpath pour Unicode. Et que vous pourriez également utiliser
si vous n'aimez pas ces noms longs
la source
Je ne sais pas trop ce qu'est une "bonne façon" de copier un fichier, mais en supposant que "bon" signifie "rapide", je pourrais élargir un peu le sujet.
Les systèmes d'exploitation actuels ont longtemps été optimisés pour gérer l'exécution de la copie du fichier de l'usine. Aucun morceau de code intelligent ne battra cela. Il est possible que certaines variantes de vos techniques de copie se révèlent plus rapides dans certains scénarios de test, mais elles se porteraient très probablement moins bien dans d'autres cas.
En règle générale, le
sendfile
fonction retourne probablement avant que l'écriture ne soit validée, donnant ainsi l'impression d'être plus rapide que les autres. Je n'ai pas lu le code, mais c'est très certainement parce qu'il alloue son propre tampon dédié, échangeant de la mémoire pour le temps. Et la raison pour laquelle cela ne fonctionnera pas pour les fichiers supérieurs à 2 Go.Tant que vous traitez avec un petit nombre de fichiers, tout se passe à l'intérieur de divers tampons (le premier du runtime C ++ si vous utilisez
iostream
, les internes du système d'exploitation, apparemment un tampon supplémentaire de la taille d'un fichier dans le cas desendfile
). Les supports de stockage réels ne sont accessibles qu'une fois que suffisamment de données ont été déplacées pour valoir la peine de faire tourner un disque dur.Je suppose que vous pourriez légèrement améliorer les performances dans des cas spécifiques. Du haut de ma tête:
copy_file
séquentiellement (bien que vous remarquerez à peine la différence tant que le fichier tient dans le cache du système d'exploitation)Mais tout cela n'entre pas dans le cadre d'une fonction de copie de fichiers à usage général.
Donc, selon mon programmeur sans doute chevronné, une copie de fichier C ++ devrait simplement utiliser la
file_copy
fonction dédiée C ++ 17 , à moins que l'on en sache plus sur le contexte dans lequel la copie de fichier se produit et que certaines stratégies intelligentes peuvent être conçues pour déjouer le système d'exploitation.la source