320 votes

Copier un fichier d’une manière saine, sûre et efficace

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

Je manque d'exemples et de recherche d'une manière qui fonctionne avec le C++.

ANSI-C-MOYEN

#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-MOYEN (K&R d'utiliser ce "The C programming language", à un niveau plus bas)

#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-CHEMIN

#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++-CHEMIN

#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++-CHEMIN

#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
  • En utilisant le niveau d'EXÉCUTION 3 (Multi-utilisateurs, Réseau, Terminal, pas d'interface graphique)
  • INTEL SSD Postville 80 GO, rempli jusqu'à 50%
  • Copie de 270 MO OGG FICHIER VIDÉO

Les étapes ro 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

Les 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 imprimer les mêmes résultats.
Le fichier vidéo est encore jouable.

Questions

  • Quelle méthode préférez-vous?
  • Connaissez-vous les meilleures solutions?
  • Voyez-vous des erreurs dans mon code?
  • Connaissez-vous une raison pour éviter une solution?

  • FSTREAM (KISS, Streambuffer)
    J'aime vraiment ça, parce que c'est vraiment court et simple. Aussi loin à l'est que je connais l'opérateur << est surchargé pour rdbuf() et ne pas convertir quoi que ce soit. - Il Correct?

Merci

Mise à jour 1
J'ai changé la source dans tous les échantillons dans ce sens, que de l'ouvrir et de fermer les descripteurs de fichiers est d'inclure dans la mesure de l' horloge(). Leur sont d'aucun autre changement important dans le code source. Les résultats n'ont pas changé! J'ai aussi utilisé le temps de vérifier mes résultats.

Mise à jour 2
C ANSI échantillon changé: l'état de L' tandis que la boucle ne demande pas plus feof() à la place j'ai déménagé fread() dans la condition. Il ressemble, le code s'exécute maintenant de 10 000 horloges plus rapide.

La mesure a changé: Les premiers résultats ont toujours été tamponnée, parce que j'ai répété l'ancienne ligne de commande rm.ogv && sync && heure ./programme pour chaque programme, un peu de temps. Maintenant, j'ai redémarrer le système pour chaque programme. Le tampon de nouveaux résultats et de montrer aucune surprise. Le tampon et les résultats n'ont pas vraiment changé.

Si je n'ai pas de supprimer l'ancienne version, les programmes de réagir différente. Écraser un fichier existant tampon est plus rapide avec la norme POSIX et SENDFILE, tous les autres programmes sont plus lents. Peut-être les options de tronquer ou de créer d'avoir un impact sur ce comportement. Mais d'écraser des fichiers existants avec la même copie n'est pas un monde réel de cas d'utilisation.

L'exécution de la copie avec le cp prend 0.44 secondes sans tampon und 0.30 seconde mémoire tampon. Si le cp est un peu plus lent que la POSIX échantillon. L'air parfait pour moi.

Peut-être que j'ajoute aussi des échantillons et des résultats de mmap() et copy_file() de boost::filesystem.

Mise à jour 3
Je l'ai mis aussi sur une page de blog et étendu un peu. Y compris splice(), qui est une fonction plus bas niveau depuis le noyau Linux. Peut-être plus d'échantillons avec Java va suivre. http://www.ttyhoney.com/blog/?page_id=69

274voto

Loki Astari Points 116129

Copier un fichier d’une manière saine d’esprit :

C’est tellement simple et intuitif à lire il vaut l’extra frais. Si nous avons été faire beaucoup je retomberaient aux OS des appels au système de fichiers. Je ne sais pas boost est une méthode de copie de fichiers dans sa catégorie de système de fichiers.

Il y a toujours les bibliothèques C permettant d’interagir avec le système de fichiers :

21voto

Potatoswatter Points 70305

De trop!

Le "ANSI C" chemin de la mémoire tampon est redondant, car un FILE est déjà mis en mémoire tampon. (La taille de ce tampon interne est ce qu' BUFSIZ définit en fait.)

Le "TAMPON-C++-WAY" sera lente car il passe par fstream, ce qui fait beaucoup de virtuel d'expédition, et maintient encore une fois des tampons internes ou chaque objet de flux de données. (Le "COPIER-ALGORITHME-C++-WAY" ne souffrent pas de ce, que l' streambuf_iterator classe contourne le flux de la couche.)

Je préfère le "COPIER-ALGORITHME-C++", mais sans la construction d'un fstream, il suffit de créer nue - std::filebuf cas lorsque aucune mise en forme est nécessaire.

Pour des performances brutes, vous ne pouvez pas battre POSIX descripteurs de fichiers. C'est laid, mais portable et rapide sur n'importe quelle plateforme.

Linux, la voie semble être incroyablement rapide - peut-être le système d'exploitation laissez-le retour de la fonction avant de l'I/O a été fini? En tout cas, ce n'est pas assez portable pour de nombreuses applications.

EDIT: Ah, "natif de Linux" est peut-être l'amélioration de la performance par l'entrelacement de lit et écrit avec asynchronous I/O. Laisser les commandes de la pile peut aider le pilote de disque de décider quand est préférable de demander. Vous pouvez essayer de Stimuler Asio ou pthreads à des fins de comparaison. Comme pour "ne pouvez pas battre POSIX descripteurs de fichier"... eh bien, c'est vrai si vous faites n'importe quoi avec les données, et pas seulement copier aveuglément.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X