Je cherche un bon moyen de copier un fichier (binaire ou texte). J'ai écrit plusieurs exemples, tous fonctionnent. Mais je veux entendre l'avis de programmeurs chevronnés.
Il me manque de bons exemples et je cherche une méthode 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 utilisent ceci dans "The C programming language", 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;
}
COPIER-ALGORITHME-C++-WAY
#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;
}
PROPRE TAMPON-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, pas d'interface graphique)
- INTEL SSD-Postville 80 GB, rempli jusqu'à 50%.
- Copie d'un fichier OGG-VIDEO de 270 MB
Étapes de la reproduction
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 (TEMPS CPU 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 donnent les mêmes résultats.
Le fichier vidéo est toujours lisible.
Questions
-
Quelle méthode préférez-vous ?
-
Connaissez-vous de meilleures solutions ?
-
Voyez-vous des erreurs dans mon code ?
-
Connaissez-vous une raison d'éviter une solution ?
-
FSTREAM (KISS, Streambuffer)
J'aime beaucoup celle-ci, car elle est vraiment courte et simple. Pour autant que je sache, l'opérateur << est surchargé pour rdbuf() et ne convertit rien. Est-ce exact ?
Merci
Mise à jour 1
J'ai modifié la source de tous les échantillons de manière à ce que l'ouverture et la fermeture des descripteurs de fichier soient incluses dans la mesure de clock() . Il n'y a pas d'autres changements significatifs dans le code source. Les résultats n'ont pas changé ! J'ai aussi utilisé temps pour revérifier mes résultats.
Mise à jour 2
L'échantillon C de l'ANSI a été modifié : l'état de la while-loop n'appelle plus feof() Au lieu de cela, j'ai déménagé fread() dans la condition. Il semble que le code s'exécute maintenant 10.000 fois plus vite.
Mesure modifiée : Les anciens résultats étaient toujours mis en mémoire tampon, car je répétais l'ancienne ligne de commande rm to.ogv && sync && time ./programme pour chaque programme plusieurs fois. Maintenant, je redémarre le système pour chaque programme. Les résultats non bufferisés 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. Écraser un fichier existant tamponné est plus rapide avec POSIX et SENDFILE, tous les autres programmes sont plus lents. Peut-être que les options tronqué o 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.
Effectuer 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'échantillon POSIX. Pour moi, tout va bien.
J'ajouterai peut-être aussi des échantillons et des résultats de mmap() et _copy_file()
_ de boost::filesystem.
Mise à jour 3
J'ai mis cela aussi 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
5 votes
fstream
est certainement une bonne option pour les opérations sur les fichiers.1 votes
richelbilderbeek.nl/CppCopy_file.htm
29 votes
Vous avez oublié la méthode paresseuse : system("cp from.ogv to.ogv") ;
0 votes
Je n'aime pas la manière paresseuse. C'est pourquoi je n'en parle pas. C'est moche. D'un autre côté, le code dans coreutils est testé et éprouvé depuis longtemps.
3 votes
#include <copyfile.h> copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);
1 votes
Pourquoi prenez-vous uniquement le temps de copier les fichiers, et non le temps d'ouverture/fermeture ? Si vous effectuez un certain nombre d'itérations, le temps de démarrage/arrêt à chaque fois que vous copiez quelque chose pourrait avoir une grande importance.
0 votes
Tu as raison Kevin. J'ai modifié les échantillons de cette façon avec la mise à jour. Mon intention précédente était que le "fichier est déjà" accessible par l'utilisateur et de surveiller uniquement le processus de copie lui-même. Mais l'ouverture/fermeture du filedescriptor fait partie du processus de copie !
1 votes
Je dirais d'utiliser
boost::filesystem::copy_file
.0 votes
Pourriez-vous s'il vous plaît corriger les "unités" des résultats de chronométrage. "Horloges par seconde" n'a aucun sens, c'est une mesure de la vitesse de l'horloge du processeur, rien à voir avec les E/S. Voulez-vous dire "horloges par fichier" ?
0 votes
J'ai changé la description en "CPU TIME", souvent appelé aussi "CPU TICKS". Cela correspond à la description de la page de manuel de clock(). C'est mieux ?
1 votes
Personnellement, je ne me déciderais qu'après avoir ajouté la gestion des erreurs à chaque exemple et comparé le code ajouté : la performance pure et simple est évidemment un facteur important dans la décision à prendre, mais elle ne devrait généralement pas être le principal facteur de décision. uniquement facteur.
1 votes
La page du blog n'existe plus. Votre article est-il encore disponible quelque part ?
0 votes
Actuellement non. Je vais remettre le blog en place dans les prochaines semaines.
0 votes
Si la source est vide, alors l'écriture de son rdbuf résultera en l'activation du failbit sur le flux de destination. Pour cette raison, vous pouvez préférer std::copy comme approche portable C++ par défaut (si vous avez l'intention d'écrire d'autres choses sur le flux de destination).
4 votes
Désolé d'intervenir si tard, mais je ne qualifierais aucune de ces solutions de "sûres", car elles ne prévoient aucune gestion des erreurs.
0 votes
Le site
sendfile
L'exemple est faux.sendfile
est déclaré commessize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
. Notez que le troisième argument estoff_t *offset
. Doncsendfile(dest, source, 0, stat_source.st_size);
aura probablement pour conséquenceSIGSEGV
.1 votes
@AndrewHenle : Je suis désolé, mais la page de manuel dit qu'il est autorisé de passer [0|NULL|nullptr] et sendfile commencera à l'offset (le début du fichier dans ce cas).
0 votes
@Peter
What method would you prefer? Do you know better solutions?
a orienté cette question, par ailleurs excellente, dans une direction fondée sur l'opinion, puisque la préférence et le "meilleur" mal défini sont subjectifs (vouliez-vous dire "plus rapide" ?). Des questions comme "Quelles autres méthodes existent ? Quels sont leurs avantages et leurs inconvénients ?" (comme votreDo you know a reason to avoid a solution?
) est plus approprié pour le SO.1 votes
@KeithM : Pour être honnête, votre suggestion semble plutôt théorique et changer ces mots n'améliorerait rien. Parce que je ne maintiens plus mon blog et la liste d'exemples est manquante. splice() (ce qui est bien) J'ai hâte d'y ajouter splice() et essayer de fusionner l'ensemble de ces éléments avec le nouveau site de documentation, je pense que ce serait vraiment une bonne solution. S'il vous plaît, soyez indulgent avec moi :)
0 votes
@Peter Théorique comment ? Vous n'avez peut-être pas compris mon point de vue ; je dis que vous posez des questions basées sur des opinions, ce qui est hors sujet pour Stack Overflow. Les réponses actuelles ne sont pas des opinions, donc le simple fait de supprimer ces deux questions améliorerait ce Q&R.
0 votes
Mesurer le temps CPU d'une opération liée aux E/S est inutile. Le seul temps intéressant est le temps écoulé par l'horloge murale.
0 votes
L'exemple de sendfile échouera sur les fichiers de plus de 2 Go.