web-dev-qa-db-fra.com

Comment puis-je regrouper plusieurs fichiers en un seul sans fichier intermédiaire?

Voici le problème auquel je suis confronté:

  • Je suis en train de traiter un fichier texte de taille ~ 100G.
  • J'essaie d'améliorer le temps d'exécution en divisant le fichier en plusieurs centaines de fichiers plus petits et en les traitant en parallèle.
  • À la fin, je récupère les fichiers résultants dans l'ordre.

Le temps de lecture/écriture du fichier lui-même prend des heures, je voudrais donc trouver un moyen d'améliorer ce qui suit:

cat file1 file2 file3 ... fileN >> newBigFile
  1. Cela nécessite le double de l'espace disque comme file1 ... fileN prend 100 Go, puis newBigFile prend encore 100 Go, puis file1... fileN est supprimé.

  2. Les données sont déjà dans file1 ... fileN, faisant le cat >> entraîne un temps de lecture et d'écriture alors que tout ce dont j'ai vraiment besoin, c'est que les centaines de fichiers réapparaissent en un seul fichier ...

21
Wing

Si vous n'avez pas besoin d'un accès aléatoire au gros fichier final (c'est-à-dire que vous ne le lisez qu'une seule fois du début à la fin), vous pouvez faire apparaître vos centaines de fichiers intermédiaires comme un seul. Où vous feriez normalement

$ consume big-file.txt

au lieu de cela

$ consume <(cat file1 file2 ... fileN)

Cela utilise Unix substitution de processus , parfois aussi appelé "canaux nommés anonymes".

Vous pouvez également économiser du temps et de l'espace en divisant votre entrée et en effectuant le traitement en même temps; GNU Parallel a un - pipe switch qui fera exactement cela. Il peut également réassembler les sorties dans un seul gros fichier, utilisant potentiellement moins d'espace de travail car il n'a besoin que de conserver nombre de cœurs sur le disque à une fois que. Si vous exécutez littéralement vos centaines de processus en même temps, Parallel améliorera considérablement votre efficacité en vous permettant de régler la quantité de parallélisme avec votre machine. Je le recommande fortement.

12
Jay Hacker

Peut-être que dd serait plus rapide car vous n'auriez pas à passer de trucs entre cat et Shell. Quelque chose comme:

mv file1 newBigFile
dd if=file2 of=newBigFile seek=$(stat -c %s newBigFile)
5
thejh

Lors de la concaténation de fichiers, vous pouvez supprimer les petits fichiers à mesure qu'ils sont ajoutés:

for file in file1 file2 file3 ... fileN; do
  cat "$file" >> bigFile && rm "$file"
done

Cela éviterait d'avoir besoin de doubler l'espace.

Il n'y a pas d'autre moyen de créer des fichiers comme par magie. L'API du système de fichiers n'a tout simplement pas de fonction qui le fait.

5
Robie Basak

Est-il possible que vous ne divisiez simplement pas le fichier? Au lieu de cela, traitez le fichier en morceaux en définissant le pointeur de fichier dans chacun de vos travailleurs parallèles. Si le fichier doit être traité de manière orientée ligne, cela le rend plus délicat mais cela peut toujours être fait. Chaque travailleur doit comprendre que plutôt que de commencer à l'offset que vous lui donnez, il doit d'abord rechercher octet par octet le prochain saut de ligne +1. Chaque travailleur doit également comprendre qu'il ne traite pas la quantité définie d'octets que vous lui donnez mais doit traiter la première nouvelle ligne après la quantité définie d'octets qu'il est alloué au traitement.

L'allocation et le réglage réels du pointeur de fichier sont assez simples. S'il y a n travailleurs, chacun traite n/octets de taille de fichier et le pointeur de fichier commence au numéro de travailleur * n/taille_fichier.

y a-t-il une raison pour laquelle ce type de plan n'est pas suffisant?

4
frankc

Solution rapide mais pas gratuite? Obtenez un disque SSD ou un stockage flash PCIe. Si c'est quelque chose qui doit être fait régulièrement, l'augmentation de la vitesse du disque IO va être l'accélération la plus rentable et la plus rapide que vous puissiez obtenir.

3
Robert P

Je pense que c'est le moyen le plus rapide de récupérer tous les fichiers contenus dans le même dossier:

$ ls [path to folder] | while read p; do cat $p; done
3
barbaric_pug

tout ce dont j'ai vraiment besoin, c'est que les centaines de fichiers réapparaissent comme 1 fichier ...

La raison pour laquelle il n'est pas pratique de simplement joindre des fichiers de cette façon au niveau du système de fichiers car les fichiers texte ne remplissent généralement pas exactement un bloc de disque, de sorte que les données des fichiers suivants devraient être remontées pour combler les lacunes, provoquant une tas de lectures/écritures de toute façon .

2
Kevin Stricker

Il existe une trop grande concurrence.

Une meilleure façon de procéder serait d'utiliser des lectures à accès aléatoire dans le fichier sur les plages souhaitées et de ne jamais le diviser et traiter uniquement le nombre de fichiers en tant que nombre de CPU/cœurs physiques dans la machine. C'est à moins que cela n'inonde également le disque avec IOPS, alors vous devez réduire jusqu'à ce que le disque ne soit pas le goulot d'étranglement.

Ce que vous faites de toute façon avec tout le fractionnement/copie/suppression naïf génère des tonnes d'IOPS et il n'y a aucun moyen de contourner la physique de celui-ci.

Une solution transparente qui représenterait probablement plus de travail que n'en vaut la peine, à moins qu'il ne s'agisse d'un problème/problème quotidien, consiste à écrire un système de fichiers Fuse personnalisé qui représente un seul fichier en tant que plusieurs fichiers. Il existe de nombreux exemples sur la façon de traiter le contenu des fichiers d'archive sous forme de fichiers individuels qui vous montreront les bases de la procédure à suivre.

2
user177800