Si vous savez qu'ils utiliseront un Linux> 2.6.17, splice()
est le moyen de faire une copie zéro sous 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);
Malheureusement, vous ne pouvez pas utiliser sendfile()
ici car la destination n'est pas un socket. (Le nom sendfile()
vient de send()
+ "fichier").
Pour zéro copie, vous pouvez utiliser splice()
comme suggéré par @Dave. (Sauf qu'il ne s'agira pas d'une copie zéro ; ce sera "une copie" du cache de page du fichier source vers le cache de page du fichier de destination.)
Cependant... (a) splice()
est spécifique à Linux ; et (b) vous pouvez presque certainement faire aussi bien en utilisant des interfaces portables, à condition de les utiliser correctement.
En bref, utilisez open()
+ read()
+ write()
avec un petit tampon temporaire. Je suggère 8K. Votre code ressemblerait donc à ceci :
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 read_result = read(in_fd, &buf[0], sizeof(buf));
if (!read_result) break;
assert(read_result > 0);
ssize_t write_result = write(out_fd, &buf[0], read_result);
assert(write_result == read_result);
}
Avec cette boucle, vous copierez 8 Ko du cache de page in_fd dans le cache CPU L1, puis l'écrivez du cache L1 dans le cache de page out_fd. Ensuite, vous écraserez cette partie du cache L1 avec le prochain morceau de 8K du fichier, et ainsi de suite. Le résultat net est que les données en buf
ne sera jamais réellement stocké dans la mémoire principale (sauf peut-être une fois à la fin) ; du point de vue de la RAM du système, c'est aussi bien que d'utiliser "zero-copy" splice()
. De plus, il est parfaitement portable sur n'importe quel système POSIX.
Notez que le petit tampon est la clé ici. Les processeurs modernes typiques ont environ 32 Ko pour le cache de données L1, donc si vous rendez le tampon trop grand, cette approche sera plus lente. Peut-être beaucoup, beaucoup plus lent. Gardez donc le tampon dans la plage "quelques kilo-octets".
Bien sûr, à moins que votre sous-système de disque ne soit très très rapide, la bande passante mémoire n'est probablement pas votre facteur limitant. Je recommanderais donc posix_fadvise
pour que le noyau sache ce que vous faites :
posix_fadvise(in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
Cela indiquera au noyau Linux que sa machinerie de lecture anticipée devrait être très agressive.
Je suggérerais également d'utiliser posix_fallocate
pour préallouer l'espace de stockage pour le fichier de destination. Cela vous indiquera à l'avance si vous manquerez de disque. Et pour un noyau moderne avec un système de fichiers moderne (comme 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 toutes grâce à la raclée TLB. (Des noyaux très récents avec des "pages énormes transparentes" pourraient atténuer cela ; je n'ai pas essayé récemment. Mais c'était certainement très mauvais avant. Je ne prendrais donc la peine de tester que mmap
si vous avez beaucoup de temps pour comparer et un noyau très récent.)
[Mise à jour]
Il y a une question dans les commentaires à savoir si splice
d'un fichier à l'autre est zéro-copie. Les développeurs du noyau Linux appellent cela "le vol de page". La page de manuel pour splice
et les commentaires dans la source du noyau indiquent que le SPLICE_F_MOVE
flag devrait fournir cette fonctionnalité.
Malheureusement, le support de SPLICE_F_MOVE
a été retiré en 2.6.21 (en 2007) et jamais remplacé. (Les commentaires dans les sources du noyau n'ont jamais été mis à jour.) Si vous recherchez les sources du noyau, vous trouverez SPLICE_F_MOVE
n'est en fait référencé nulle part. Le dernier message que je peux trouver (de 2008) dit qu'il est "en attente d'un remplacement".
L'essentiel est que splice
d'un fichier à un autre appelle memcpy
déplacer les données ; ce n'est pas zéro-copie. Ce n'est pas beaucoup mieux que ce que vous pouvez faire dans l'espace utilisateur en utilisant read
/write
avec de petits tampons, alors autant s'en tenir aux interfaces standard et portables.
Si le "vol de page" est rajouté dans le noyau Linux, alors les avantages de splice
serait beaucoup plus grand. (Et même aujourd'hui, lorsque la destination est un socket, vous obtenez une vraie copie zéro, ce qui fait splice
plus attrayant.) Mais pour les besoins de cette question, splice
ne vous achète pas beaucoup.