web-dev-qa-db-fra.com

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

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 des programmeurs chevronnés.

Je manque de bons exemples et cherche une manière 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;
}

COPY-ALGORITHM-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;
}

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 // requiert 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 (multiutilisateur, réseau, terminal, pas d’interface graphique)
  • INTEL SSD-Postville 80 Go, rempli à 50%
  • Copier un fichier vidéo OGG 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.

Questions

  • Quelle méthode préféreriez-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 celui-ci, car il est vraiment court et simple. Autant que je sache, l'opérateur << est surchargé pour rdbuf () et ne convertit rien. Correct?

Merci

Mise à jour 1
J'ai changé la source de tous les échantillons de cette manière, afin 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 ne changent pas! J'ai également utilisé le temps pour 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 fonctionne maintenant 10 000 heures plus rapidement.

Mesure modifiée: les résultats précédents étaient toujours mis en mémoire tampon, car je répétais l'ancienne ligne de commande rm to.ogv && sync && time ./program plusieurs fois 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. Remplacer un fichier existant mis en mémoire tampon 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 mémoire tampon et 0,30 seconde en mémoire tampon. Donc cp est un peu plus lent que l'exemple POSIX. Ça a l'air bien pour moi.

Peut-être que j’ajoute aussi des échantillons et des résultats de , mmap () et copy_file() de boost :: système de fichiers.

Mise à jour 3
J'ai également ajouté cela à une page de blog. Y compris splice () , qui est une fonction de bas niveau du noyau Linux. Peut-être que d'autres exemples avec Java suivront. http://www.ttyhoney.com/blog/?page_id=69

285
Peter

Copier un fichier de manière saine:

#include <fstream>

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 souvent, il serait préférable de faire appel aux systèmes d’exploitation du système de fichiers. Je suis sûr que 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:

#include <copyfile.h>

int
copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);
237
Martin York

Avec C++ 17, le moyen standard de copier un fichier consiste à inclure l’en-tête <filesystem> et à utiliser:

_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);
_

Le premier formulaire est équivalent au deuxième avec _copy_options::none_ utilisé comme options (voir aussi copy_file ).

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

49
manlio

Trop!

La mémoire tampon "ANSI C" est redondante, car un FILE est déjà mis en mémoire tampon. (La taille de ce tampon interne correspond à ce que BUFSIZ définit en réalité.)

"OWN-BUFFER-C++ - WAY" sera lent lorsqu’il passera par fstream, qui effectue beaucoup de dispatching virtuel et conserve à nouveau les tampons internes ou chaque objet de flux. ("COPY-ALGORITHM-C++ - WAY" n'en souffre pas, car la classe streambuf_iterator contourne la couche de flux.)

Je préfère le "COPY-ALGORITHM-C++ - WAY", mais sans construire un fstream, créez simplement des instances nues std::filebuf lorsqu'aucun formatage réel n'est nécessaire.

Pour des performances brutes, vous ne pouvez pas battre les descripteurs de fichier POSIX. C'est moche mais portable et rapide sur n'importe quelle plate-forme.

La manière dont Linux semble être incroyablement rapide - peut-être que le système d’exploitation a laissé la fonction revenir avant la fin de l’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 entrelacement des lectures et des écritures avec des E/S asynchrones. Laisser les commandes s'accumuler peut aider le pilote de disque à décider quand chercher. Vous pouvez essayer Boost Asio ou pthreads à des fins de comparaison. Quant à "je ne peux pas battre les descripteurs de fichier POSIX" ... eh bien, c’est vrai si vous faites quelque chose avec les données, pas seulement la copie à l'aveugle.

20
Potatoswatter

Je veux faire la très remarque importante que la méthode LINUX utilisant sendfile () pose un problème majeur en ce sens qu'elle ne peut pas copier des fichiers de plus de 2 Go. ! Je l'avais implémenté suite à cette question et je 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

sendfile () transférera au plus 0x7ffff000 (2 147 479 552) octets, en renvoyant le nombre d'octets réellement transférés. (Ceci est vrai pour les systèmes 32 bits et 64 bits.)

14
rveale

Qt a une méthode pour copier des fichiers:

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

Notez que pour utiliser cela, vous devez installer Qt (instructions ici ) et l'inclure dans votre projet (si vous utilisez Windows et que vous n'êtes pas administrateur, vous peut télécharger Qt ici à la place). Voir aussi cette réponse .

2
Donald Duck

Pour ceux qui aiment boost:

boost::filesystem::path mySourcePath("foo.bar");
boost::filesystem::path myTargetPath("bar.foo");

// Variant 1: Overwrite existing
boost::filesystem::copy_file(mySourcePath, myTargetPath, boost::filesystem::copy_option::overwrite_if_exists);

// Variant 2: Fail if exists
boost::filesystem::copy_file(mySourcePath, myTargetPath, boost::filesystem::copy_option::fail_if_exists);

Notez que boost :: filesystem :: path est également disponible en tant que wpath pour Unicode. Et que vous puissiez aussi utiliser

using namespace boost::filesystem

si vous n'aimez pas ces noms de type longs

1
anhoppe