@Brunod, @Revan... discussion intéressante sur les
"performances" maximales !
Mais en réalité il n'y a pas de "meilleur" truc, tout dépend du "use case" (cas d'utilisation), notamment son matériel et ce qui est le "maillon faible".
J'avais réalisé un moteur de copie optimisé sur CTOS (un O.S. hélas disparu)... il y a une vingtaine d'années !.. Et pour le "use case" à l'époque c'était fort intéressant par rapport à la copie basique du système.
Que se passe-t-il dans un programme de copie "naïf" (comme celui de Windows... et d'où l'intérêt de SC sur Windows), lorsqu'on copie un fichier :
Début :
- Lecture bloc fichier source
- Ecriture bloc fichier destination
=> tant qu'il reste des octets à écrire, retourner à début.
... et voila ! Ça s'appelle de la copie en série. Et c'est raisonnablement efficace dans 90% des cas où on copie un fichier d'un disque sur lui-même.
Simplement ce même algorithme s'avère largement sous-optimisé lorsqu'on copie entre plusieurs périphériques différents : disque à disque, disque à clé USB, disque à disque via réseau, etc...
Pour comprendre pourquoi il faut descendre au niveau physique de la chose.
Commençons par le cas simple :
- Copie d'un fichier d'un disque physique sur lui-même :
On suppose qu'il s'agit là d'un disque physique magnétique avec des plateaux et des têtes de lecture.
L'opération de copie va donc nécessiter de déplacer la tête à l'endroit à lire, lire un bloc, déplacer la tête à l'endroit à écrire, écrire un bloc, etc... jusqu'à ce qu'on ait fini la copie.
Le temps CPU est négligeable (une boucle simple, et des DMA pour lire/écrire) et donc le "maillon faible" en l'occurrence est le disque physique qui va devoir lire/écrire et déplacer les têtes.
Les optimisations que l'on peut faire dépendent de la taille des fichiers.
Sur les gros fichiers, il n'y a pas grand chose à faire en réalité.
Le temps de copie peut se calculer assez simplement à partir du temps d'accès moyen de déplacement des têtes et des vitesses moyennes de lecture/écriture des données.
Comme la taille des données à lire et à écrire est constante pour une copie donnée, le seul facteur de gain est donc de diminuer le nombre de déplacement de têtes, c'est à dire d'augmenter la taille du buffer de copie.
La seule optimisation plus "fine" serait d'essayer de "placer" le fichier destination au plus près du fichier source sur le disque pour limiter le déplacement des têtes. Mais là on est plutôt dans une optimisation de filesystem que dans une optimisation du niveau d'un "copieur".
On aura aussi des résultat assez différents selon la "fragmentation" qui génère inévitablement des déplacements de tête même si le fichier est tout lu/écrit en mémoire.
Sur les petits fichiers, il y a probablement une taille optimale de buffer et de regroupement, mais elle dépend probablement du type de formatage. En effet la lecture et l'écriture de plusieurs petits fichiers va générer des I/O supplémentaires pour aller lire les structures de contrôles du disque.
Notons que dans ce cas
la parallélisation est contre-productive ! En effet, supposons que je coupe mon gros fichier en deux et fasse la copie des deux parties en parallèle, eh bien je vais générer tout un tas de déplacement de têtes qui sont préjudiciable à la rapidité de la copie.
Pour vous en convaincre c'est très simple.
- Ouvrez un terminal
- Lancez une copie d'un gros fichier (3 ou 4Go par exemple) et chronométrez.
- Une fois la première copie finie, lancez une copie d'un autre gros fichier et chronométrez.
- Ajoutez les deux temps.
- Supprimez vos deux copies.
- Refaites maintenant la même chose avec deux fenêtres de terminal ouvertes en même temps et en faisant les deux copies au même moment, chronométrez.
- Vous devriez observer un temps notablement supérieur au premier chrono (je suppose bien sûr que les copies sont toutes sur le même disque physique, et que votre disque dispose de suffisamment d'espace pour qu'une fragmentation excessive ne casse pas tout !)
Conclusion de ce use-case : un "SuperCopieur" ne fait quasiment rien gagner par rapport à la copie système classique (éventuel gain sur une copie de multiple de petits fichiers... mais pas sûr que du rsync ou des utilitaires standards ne fassent pas jeu égal voire mieux dans ce cas là !)
Cas plus intéressant
- Copie d'un fichier d'un disque physique sur un autre support physique :
Là c'est nettement plus amusant.
On a maintenant 3 facteurs :
- Vitesse de lecture
- Vitesse de transfert des données
- Vitesse d'écriture
Et là, les programmes de copie optimisés ont un avantage... à condition qu'ils parallélisent (comme vous l'avez observé).
La vitesse que l'on peut obtenir va dépendre du "maillon faible".
Supposons que mes deux supports physiques soient sur deux machines séparées par un réseau local.
Dans ce cas, le "maillon faible" pourra être le réseau local.
Un disque dur moyen sait lire/écrire entre 50 et 150MB/sec.
Si vous êtes sur un réseau local en 100Mbps, la vitesse maximale théorique est de 12,5MB/s et en pratique on plafonne autour de 11,5MB/s en NFS (le plus efficace) une fois retiré les overhead protocolaires.
Par conséquent, dans ce cas : copie d'un disque 1 à un disque 2 au travers d'un réseau à 100Mbps, votre vitesse maxi est la vitesse du réseau.
Or un programme de copie classique "série" perd du temps.
En effet, dans ma "boucle" naïve ci-dessus,
- je lis un bloc (et j'attends qu'il soit lu)
- ensuite je l'écris (ce qui nécessite au préalable de le transférer).
... puis je boucle.
J'ai donc :
- Temps lecture d'un bloc
- Temps de transfert
- Temps d'écriture d'un bloc
multiplié par le nombre de blocs.
Donc très exactement, on ajoute les 3 temps : lecture, écriture, transfert.
Finalement tout ceci est assez nigaud lorsqu'on considère que dans ce cas là les périphériques sont indépendants et pourraient lire et écrire en parallèle. Le réseau étant aussi indépendant du disque, il est possible de transférer le bloc N pendant que je lis le bloc N+1.
Un programme de copie optimisé devra donc dans ce cas :
- Allouer "un certain nombre" de blocs (mais pas trop gros... c'est contre-productif)
- Lire de façon "asynchrone" le fichier source dans les blocs
- Dès qu'un bloc de lecture est totalement rempli, on l'envoie à l'écriture.
- Lorsqu'une écriture est finie, on libère le bloc pour le rendre au process de lecture.
Pourquoi pas des "gros blocs". A l'extrême si j'ai suffisamment de mémoire pour lire tout mon fichier source en RAM et que je fais ainsi, je vais retomber dans le cas précédent de cumul des temps et on ne va pas paralléliser du tout !..
Alors ceci est la théorie, et c'était très utile sur CTOS (le gain de temps était manifeste) et toujours utile sur Windows.
Sur Linux c'est beaucoup moins utile... Pourquoi ?.. Eh bien parce que Linux est bien mieux que Windows et dispose de "buffers" considérables pour les copies.
C'est potentiellement différent d'un système à l'autre, mais sur mes PC à 4G de RAM (assez standard maintenant) j'observe un buffer de 256MB.
Pour l'observer c'est assez simple : faites une copie d'un gros fichier (par ex. 1GB) d'un disque à une clé USB. Vous allez voir les premiers 256MB se copier à toute vitesse -en réalité à la vitesse de lecture de votre disque qui est en général bien plus rapide que votre clé USB-, et ensuite le machin ralentit considérablement.
Pourquoi : tout simplement quand le système vous dit que les premiers 256MB sont copiés en réalité ils ne le sont pas réellement, ils sont juste dans le buffer de copie !.. D'ailleurs à la fin de la copie vous verrez le système "coincé" plusieurs secondes autour de 100%, c'est le temps de vidage du buffer.
Donc en réalité ce gros "buffer" joue tout à fait son rôle de parallélisation et on gagne assez marginalement à en rajouter.
Conclusion finale... "hélas" pour votre projet, Linux est tellement bien fait qu'il y a bien moins à grapiller en performances que sur des systèmes Windows avec un "SuperCopier" !..
... et c'est sûr aussi il n'y a pas que les performances, on peut certainement apporter des améliorations ergonomiques à la copie, notamment la possibilité de la mettre en pause si on a un truc urgent à faire, une grosse copie ayant tendance à sérieusement ralentir un système qui chercherait à faire des I/O sur le même disque.