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
É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
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
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);
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.
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.
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.)
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 .
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