Copier un fichier de manière saine, sûre et efficace

Je recherche un bon moyen de copier un fichier (binary ou texte). J’ai écrit plusieurs échantillons, tout le monde travaille. Mais je veux entendre l’avis des programmeurs chevronnés.

Je manque de bons exemples et cherche un moyen qui fonctionne avec C ++.

ANSI-C-WAY

#include  #include  // fopen, fclose, fread, fwrite, BUFSIZ #include  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(end - start) / CLOCKS_PER_SEC << "\n"; return 0; } 

POSIX-WAY (K & R l’utilise dans “Le langage de programmation C”, plus bas niveau)

 #include  #include  // open #include  // read, write, close #include  // BUFSIZ #include  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(end - start) / CLOCKS_PER_SEC << "\n"; return 0; } 

KISS-C ++ – Streambuffer-WAY

 #include  #include  #include  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(end - start) / CLOCKS_PER_SEC << "\n"; return 0; } 

COPY-ALGORITHM-C ++ – WAY

 #include  #include  #include  #include  #include  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 begin_source(source); istreambuf_iterator end_source; ostreambuf_iterator 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(end - start) / CLOCKS_PER_SEC << "\n"; return 0; } 

OWN-BUFFER-C ++ – WAY

 #include  #include  #include  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(end - start) / CLOCKS_PER_SEC << "\n"; return 0; } 

LINUX-WAY // requirejs un kernel> = 2.6.33

 #include  #include  // sendfile #include  // open #include  // close #include  // fstat #include  // fstat #include  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(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 Go, rempli à 50%
  • Copier un fichier OGG-VIDEO-FILE de 270 Mo

Étapes pour 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 (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 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 d’éviter une solution?

  • FSTREAM (KISS, Streambuffer)
    J’aime vraiment celui-ci, car il est vraiment court et simple. Pour autant que je sache, l’opérateur << est surchargé pour rdbuf () et ne convertit rien. Correct?

Merci

Mise à jour 1
J’ai modifié la source dans tous les échantillons de cette manière, pour que l’ouverture et la fermeture des descripteurs de fichiers soient incluses dans la mesure de l’ horloge () . Il n’y a pas d’autres changements significatifs dans le code source. Le résultat n’a pas changé! J’ai également pris le temps de vérifier mes résultats.

Mise à jour 2
Exemple 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 tourne maintenant 10 000 horloges plus rapidement.

Modification de la mesure: les anciens résultats ont toujours été mis en mémoire tampon, car j’ai répété plusieurs fois la vieille 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 non tamponnés sont nouveaux et ne surprennent pas. 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 tampon existant est plus rapide avec POSIX et SENDFILE, tous les autres programmes sont plus lents. Les options tronquer ou créer ont peut-être 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. Ça me va.

J’ajoute 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 l’ai étendu un peu. Y compris splice () , qui est une fonction de bas niveau du kernel Linux. Peut-être que d’autres échantillons avec Java suivront. http://www.ttyhoney.com/blog/?page_id=69

Copiez un fichier de manière saine:

 int main() { std::ifstream src("from.ogv", std::ios::binary); std::ofstream dst("to.ogv", std::ios::binary); dst << src.rdbuf(); } 

C'est si simple et intuitif à lire que cela vaut le coût supplémentaire. Si nous le faisions beaucoup, mieux vaut recourir aux appels au système de fichiers. Je suis sûr que boost a une méthode de copie dans sa classe de système de fichiers.

Il existe une méthode C pour interagir avec le système de fichiers:

 #include  int copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags); 

Avec C ++ 17, la méthode standard pour copier un fichier comprendra l’en-tête et utilisera:

 bool copy_file( const std::filesystem::path& from, const std::filesystem::path& to); bool copy_file( const std::filesystem::path& from, const std::filesystem::path& to, std::filesystem::copy_options options); 

La première forme est équivalente à la seconde avec copy_options::none utilisée comme options (voir aussi copy_file ).

La bibliothèque de filesystem a été initialement développée en tant que boost.filesystem et finalement fusionnée en ISO C ++ à partir de C ++ 17.

Trop!

Le tampon de la méthode “ANSI C” est redondant, car un FILE est déjà mis en mémoire tampon. (La taille de ce tampon interne est ce BUFSIZ définit réellement BUFSIZ .)

Le “OWN-BUFFER-C ++ – WAY” sera lent car il passe par fstream , qui fait beaucoup de dispatching virtuel, et maintient à nouveau les tampons internes ou chaque object de stream. (Le “COPY-ALGORITHM-C ++ – WAY” n’en souffre pas, car la classe streambuf_iterator contourne la couche de stream.)

Je préfère le “COPY-ALGORITHM-C ++ – WAY”, mais sans construire de fstream , il suffit de créer des instances nues de std::filebuf quand aucun formatage 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 plate-forme.

La méthode Linux semble être incroyablement rapide – peut-être que le système d’exploitation permet à la fonction de 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 des lectures et des écritures avec des E / S asynchrones. Laisser les commandes s’emstackr peut aider le pilote du disque à décider quand il est préférable de chercher. Vous pouvez essayer Boost Asio ou pthreads à titre de comparaison. En ce qui concerne “ne peut pas battre les descripteurs de fichiers POSIX” … eh bien c’est vrai si vous faites n’importe quoi avec les données, pas seulement en copie aveugle.

Je tiens à faire la remarque très importante que la méthode LINUX utilisant sendfile () a un problème majeur en ce sens qu’elle ne peut pas copier des fichiers de plus de 2 Go de taille! Je l’avais implémenté suite à cette question et j’ai rencontré des problèmes car je l’utilisais pour copier des fichiers HDF5 de plusieurs Go.

http://man7.org/linux/man-pages/man2/sendfile.2.html

sendfile () transfère au maximum 0x7ffff000 (2,147,479,552) octets, en renvoyant le nombre d’octets réellement transférés. (Cela est vrai sur les systèmes 32 bits et 64 bits.)

Qt a une méthode pour copier des fichiers:

 #include  QFile::copy("originalFile.example","copiedFile.example"); 

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 ). Voir aussi cette réponse .