web-dev-qa-db-fra.com

Le moyen le plus efficace de copier un fichier sous Linux

Je travaille sur un gestionnaire de fichiers indépendant du système d’exploitation et je cherche le moyen le plus efficace de copier un fichier pour Linux. Windows a une fonction intégrée, CopyFileEx () , mais 'remarquons qu'il n'existe pas de telle fonction standard pour Linux. Donc, je suppose que je devrai mettre en œuvre le mien. La manière évidente est fopen/fread/fwrite, mais existe-t-il une meilleure façon (plus rapide) de le faire? Je dois également pouvoir m'arrêter de temps en temps pour pouvoir mettre à jour le compte "copié jusqu'à présent" pour le menu de progression du fichier.

27
Radu

Malheureusement, vous ne pouvez pas utiliser sendfile() ici car la destination n'est pas un socket. (Le nom sendfile() provient de send() + "fichier").

Pour zéro-copie, vous pouvez utiliser splice() comme suggéré par @Dave. (Sauf que ce ne sera pas une copie zéro; ce sera "une copie" du cache de page du fichier source au cache de page du fichier de destination.)

Cependant ... (a) splice() est spécifique à Linux; et (b) vous pouvez certainement aussi bien utiliser des interfaces portables, à condition de les utiliser correctement.

En bref, utilisez open() + read() + write() avec un petit tampon temporaire. Je suggère 8K. Donc, votre code ressemblerait à quelque chose comme ça:

int in_fd = open("source", O_RDONLY);
assert(in_fd >= 0);
int out_fd = open("dest", O_WRONLY);
assert(out_fd >= 0);
char buf[8192];

while (1) {
    ssize_t result = read(in_fd, &buf[0], sizeof(buf));
    if (!result) break;
    assert(result > 0);
    assert(write(out_fd, &buf[0], result) == result);
}

Avec cette boucle, vous copiez 8K du cache de page in_fd dans le cache L1 de la CPU, puis vous l'écrivez du cache L1 dans le cache de page out_fd. Ensuite, vous écraserez cette partie du cache L1 par le prochain bloc de 8 Ko du fichier, et ainsi de suite. Le résultat net est que les données dans buf ne seront jamais réellement stockées dans la mémoire principale (sauf peut-être une fois à la fin); Du point de vue de la RAM système, c'est aussi bien que d'utiliser la fonction "zéro-copie" splice(). De plus, il est parfaitement portable pour tout système POSIX.

Notez que le petit tampon est la clé ici. Les processeurs modernes classiques ont environ 32 Ko pour le cache de données L1. Par conséquent, si vous agrandissez trop le tampon, cette approche sera plus lente. Peut-être beaucoup, beaucoup plus lentement. Alors gardez la mémoire tampon dans la plage "quelques kilo-octets".

Bien entendu, à moins que votre sous-système de disque ne soit très rapide, la bande passante mémoire n'est probablement pas votre facteur limitant. Je recommanderais donc (posix_fadvise } _ de laisser le noyau savoir ce que vous faites:

posix_fadvise(in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);

Cela indiquera au noyau Linux que ses machines à lecture anticipée devraient être très agressives.

Je suggérerais également d'utiliser posix_fallocate pour préallouer le stockage pour le fichier de destination. Cela vous dira à l'avance si vous allez manquer de disque. Et pour un noyau moderne avec un système de fichiers moderne (tel que XFS), cela aidera à réduire la fragmentation dans le fichier de destination.

La dernière chose que je recommanderais est mmap. C’est généralement l’approche la plus lente de tous, grâce à la dérive de TLB. (Des noyaux très récents avec des "pages gigantesques transparentes" pourraient atténuer cela; je n'ai pas essayé récemment. Mais c'était certainement très mauvais. Je ne prendrais donc la peine de tester mmap si vous avez beaucoup de temps pour analyser et un noyau très récent.)

[Mettre à jour]

Dans les commentaires, on se demande si splice d'un fichier à un autre est une copie zéro. Les développeurs du noyau Linux appellent cela "vol de page". La page de manuel relative à splice et le commentaires dans la source du noyau indiquent que le drapeau SPLICE_F_MOVE devrait fournir cette fonctionnalité.

Malheureusement, le support pour SPLICE_F_MOVE était de retiré en 2.6.21 (en 2007) } et n'a jamais été remplacé. (Les commentaires dans les sources du noyau n'ont jamais été mis à jour.) Si vous effectuez une recherche dans les sources du noyau, vous constaterez que SPLICE_F_MOVE n'est en réalité référencé nulle part. Le dernier message que je peux trouver } (de 2008) indique qu'il "attend un remplacement".

L'essentiel est que splice d'un fichier à un autre appelle memcpy pour déplacer les données; c'est pas zéro copie. Ce n’est pas beaucoup mieux que dans l’espace utilisateur en utilisant read/write avec de petits tampons, de sorte que vous feriez bien de vous en tenir aux interfaces portables standard.

Si "le vol de page" est de nouveau ajouté dans le noyau Linux, les avantages de splice seraient bien plus importants. (Et même aujourd'hui, lorsque la destination est une socket, vous obtenez une copie zéro vraie, ce qui rend splice plus attrayant.) Mais pour les besoins de cette question, splice ne vous achète pas beaucoup.

35
Nemo

Utilisez open/read/write - ils évitent la mise en mémoire tampon au niveau de la bibliothèque effectuée par fopen et ses amis.

Si vous utilisez GLib, vous pouvez également utiliser sa fonction g_copy_file.

Enfin, ce qui pourrait être plus rapide, mais il devrait être testé pour être sûr: utilisez open et mmap pour mapper en mémoire le fichier d'entrée, puis write de la région de la mémoire au fichier de sortie. Vous voudrez probablement garder ouvert/lire/écrire comme solution de secours, car cette méthode est limitée à la taille de l'espace d'adressage de votre processus.

Modifier: réponse originale suggéré de mapper les deux fichiers; @bdonlan a formulé une excellente suggestion en commentant la carte.

5
Michael Ekstrand

Si vous savez qu'ils utiliseront un linux> 2.6.17, splice() est le moyen d'effectuer la copie zéro dans linux:

 //using some default parameters for clarity below. Don't do this in production.
 #define splice(a, b, c) splice(a, 0, b, 0, c, 0)
 int p[2];
 pipe(p);
 int out = open(OUTFILE, O_WRONLY);
 int in = open(INFILE, O_RDONLY)
 while(splice(p[0], out, splice(in, p[1], 4096))>0);
4
Dave

Vous voudrez peut-être analyser la commande dd

0
ennuikiller

Ma réponse d'un duplicata plus récent de ce post.

boost propose maintenant mapped_file_source qui modélise de manière portable un fichier mappé en mémoire.

Peut-être pas aussi efficace que CopyFileEx() et splice(), mais portable et succinct.

Ce programme prend 2 arguments de nom de fichier. Il copie la première moitié du fichier source dans le fichier de destination.

#include <boost/iostreams/device/mapped_file.hpp>
#include <iostream>
#include <fstream>
#include <cstdio>

namespace iostreams = boost::iostreams;
int main(int argc, char** argv)
{
    if (argc != 3)
    {
        std::cerr << "usage: " << argv[0] << " <infile> <outfile> - copies half of the infile to outfile" << std::endl;
        std::exit(100);
    }

    auto source = iostreams::mapped_file_source(argv[1]);
    auto dest = std::ofstream(argv[2], std::ios::binary);
    dest.exceptions(std::ios::failbit | std::ios::badbit);
    auto first = source. begin();
    auto bytes = source.size() / 2;
    dest.write(first, bytes);
}

Selon le système d'exploitation, votre kilométrage peut varier en fonction des appels système tels que splice et sendfile , mais notez les commentaires dans la page de manuel:

Les applications peuvent souhaiter revenir à read (2)/write (2) dans le cas où sendfile () échoue avec EINVAL ou ENOSYS.

0
Richard Hodges