Performance de la boucle contre l'expansion

9

Besoin de suggestions d'experts sur la comparaison ci-dessous:

Segment de code utilisant une boucle:

for file in `cat large_file_list`
do
    gzip -d $file
done

Segment de code utilisant une expansion simple:

gzip -d `cat large_file_list`

Lequel sera le plus rapide? Vous devez manipuler un grand ensemble de données.

Léon
la source
1
La bonne réponse dépendra du temps nécessaire pour démarrer gzipsur votre système, du nombre de fichiers dans la liste des fichiers et de la taille de ces fichiers.
Kusalananda
La liste des fichiers contiendra environ 1000 à 10000 fichiers. La taille varie de quelques kilo-octets à 500 Mo. Je n'ai aucune idée du temps qu'il faut pour démarrer gzip dans mon système. de toute façon vérifier?
Leon
1
Ok, alors cela peut aussi dépendre de la longueur des noms de fichiers . Si les noms de fichiers sont longs, certains systèmes peuvent générer une erreur "liste d'arguments trop longue" si vous essayez de le faire sans boucle car la substitution de commande entraînerait une ligne de commande trop longue pour que le shell s'exécute. Si vous ne voulez pas dépendre du nombre de fichiers dans la liste, utilisez simplement une boucle. Consacrez-vous beaucoup de temps à décompresser ces fichiers par rapport aux autres traitements que vous effectuerez sur eux?
Kusalananda
Leon jette un œil à mes résultats de test: "énorme-arglist" est 20 fois plus rapide que "boucle" dans mon environnement.
pour un juste milieu entre le début du processus et la longueur de la ligne de commande, utilisez quelque chose comme xargs gzip -d < large_file_listmais faites attention aux espaces dans les noms de fichiers, peut-être avectr \\n \\0 large_file_list | xargs -0 gzip -d
w00t

Réponses:

19

Complications

Les éléments suivants ne fonctionnent que parfois:

gzip -d `cat large_file_list`

Trois problèmes se posent (dans la bashplupart des autres obus de type Bourne):

  1. Il échouera si un nom de fichier contient un espace ou des caractères de nouvelle ligne (en supposant qu'il $IFSn'a pas été modifié). Cela est dû au fractionnement du mot du shell .

  2. Il est également susceptible d'échouer si un nom de fichier contient des caractères globaux. Cela est dû au fait que le shell appliquera l' extension de nom de chemin à la liste de fichiers.

  3. Il échouera également si les noms de fichiers commencent par -(si POSIXLY_CORRECT=1cela ne s'applique qu'au premier fichier) ou si un nom de fichier l'est -.

  4. Il échouera également s'il contient trop de noms de fichiers pour tenir sur une seule ligne de commande.

Le code ci-dessous est soumis aux mêmes problèmes que le code ci-dessus (sauf pour le quatrième)

for file in `cat large_file_list`
do
    gzip -d $file
done

Solution fiable

Si votre large_file_lista exactement un nom de fichier par ligne, et qu'un fichier appelé -n'en fait pas partie, et que vous êtes sur un système GNU, utilisez:

xargs -rd'\n' gzip -d -- <large_file_list

-d'\n'indique xargsde traiter chaque ligne d'entrée comme un nom de fichier distinct.

-rindique de xargsne pas exécuter la commande si le fichier d'entrée est vide.

--indique gzipque les arguments suivants ne doivent pas être traités comme des options même s'ils commencent par -. -seul serait toujours traité comme -au lieu du fichier appelé -.

xargsmettra de nombreux noms de fichiers sur chaque ligne de commande, mais pas tellement qu'il dépasse la limite de ligne de commande. Cela réduit le nombre de fois qu'un gzipprocessus doit être démarré et rend donc cela rapide. Il est également sûr: les noms de fichiers seront également protégés contre le fractionnement de mots et l' expansion des noms de chemin .

John1024
la source
Merci pour la réponse détaillée. Je comprends vos 3 problèmes mentionnés. Le nom de fichier est simple et ne fera pas face à ces défis car la liste contiendra jusqu'à 20000. Et ma question concerne essentiellement les performances de ces deux segments. Merci.
Leon
1
@Leon La forboucle sera - de loin - la plus lente. Les deux autres méthodes seront très proches l'une de l'autre.
John1024
7
Ne négligez pas non plus les problèmes potentiels: de nombreuses questions ici sur StackExchange sont dues au fait que le fractionnement de mots ou l' expansion des noms de chemin est arrivé à des gens qui ne s'y attendaient pas.
John1024
5
Notez également qu'il existe des variations sur la lecture d'un fichier avec xargs: au moins la version GNU a une --arg-fileoption (forme courte -a). On pourrait donc faire à la xargs -a large_file_list -rd'\n' gzip -d place. En effet, il n'y a pas de différence, mis à part le fait qu'il <est un opérateur shell et ferait xargslire depuis stdin (qui shell "lie" au fichier), tout en -aferait ouvrir xargsexplicitement le fichier en question
Sergiy Kolodyazhnyy
2
terdon a noté dans un autre commentaire sur l'utilisation parallelde plusieurs copies de gzip, mais xargs(au moins celui de GNU), a aussi le -Pcommutateur pour cela. Sur les machines multicœurs, cela pourrait faire la différence. Mais il est également possible que la décompression soit complètement liée aux E / S de toute façon.
ilkkachu
12

Je doute que cela importerait beaucoup.

J'utiliserais une boucle, juste parce que je ne sais pas combien de fichiers sont répertoriés dans le fichier de liste, et je ne sais pas (généralement) si l'un des noms de fichiers a des espaces dans leurs noms. Faire une substitution de commande qui générerait une très longue liste d'arguments peut entraîner une erreur «Liste d'arguments trop longue» lorsque la longueur de la liste générée est trop longue.

Ma boucle ressemblerait

while IFS= read -r name; do
    gunzip "$name"
done <file.list

Cela me permettrait en outre d'insérer des commandes pour traiter les données après la gunzipcommande. En fait, selon ce que sont réellement les données et ce qui doit être fait avec elles, il peut même être possible de les traiter sans les enregistrer du tout:

while IFS= read -r name; do
    zcat "$name" | process_data
done <file.list

(où se process_datatrouve un pipeline qui lit les données non compressées à partir de l'entrée standard)

Si le traitement des données prend plus de temps que leur décompression, la question de savoir si une boucle est plus efficace ou non devient hors de propos.

Idéalement , je préférerais ne pas travailler sur une liste de noms de fichiers et utiliser plutôt un modèle de globbing de nom de fichier, comme dans

for name in ./*.gz; do
    # processing of "$name" here
done

./*.gzest un modèle qui correspond aux fichiers pertinents. De cette façon, nous ne dépendons pas du nombre de fichiers ni des caractères utilisés dans les noms de fichiers (ils peuvent contenir des sauts de ligne ou d'autres espaces, ou commencer par des tirets, etc.)

En relation:

Kusalananda
la source
5

Sur ces deux, celui avec tous les fichiers passés à une seule invocation de gzipsera probablement plus rapide, précisément parce que vous n'avez besoin de lancer gzipqu'une seule fois. (Autrement dit, si la commande fonctionne, consultez les autres réponses pour les mises en garde.)

Mais je voudrais rappeler la règle d'or de l'optimisation : ne le faites pas prématurément.

  1. N'optimisez pas ce genre de chose avant de savoir que c'est un problème.

    Cette partie du programme prend-elle du temps? Eh bien, la décompression de gros fichiers pourrait le faire, et vous devrez le faire de toute façon, donc ce ne sera peut-être pas si facile de répondre.

  2. Mesure. Vraiment, c'est le meilleur moyen d'en être sûr.

    Vous verrez les résultats de vos propres yeux (ou avec votre propre chronomètre), et ils s'appliqueront à votre situation, ce que les réponses aléatoires sur Internet pourraient ne pas faire. Mettez les deux variantes dans des scripts et exécutez time script1.sh, et time script2.sh. (Faites cela avec une liste de fichiers compressés vides pour mesurer la quantité absolue de la surcharge.)

ilkkachu
la source
0

Quelle est la vitesse de votre disque?

Cela devrait utiliser tous vos processeurs:

parallel -X gzip -d :::: large_file_list

Votre limite sera donc probablement la vitesse de votre disque.

Vous pouvez essayer de régler avec -j:

parallel -j50% -X gzip -d :::: large_file_list

Cela exécutera la moitié des travaux en parallèle comme la commande précédente et stressera moins votre disque, donc cela peut être plus rapide en fonction de votre disque.

Ole Tange
la source