Pourquoi `trouve. -type f` prend plus de temps que `find '?

15

Il semble qu'il findfaudrait de toute façon vérifier si un chemin donné correspond à un fichier ou un répertoire afin de parcourir récursivement le contenu des répertoires.

Voici une motivation et ce que j'ai fait localement pour me convaincre que c'est find . -type fvraiment plus lent que find .. Je n'ai pas encore fouillé dans le code source de GNU find.

Je sauvegarde donc certains fichiers de mon $HOME/Workspacerépertoire et j'exclus les fichiers qui sont soit des dépendances de mes projets, soit des fichiers de contrôle de version.

J'ai donc exécuté la commande suivante qui s'est exécutée rapidement

% find Workspace/ | grep -v '/vendor\|/node_modules/\|Workspace/sources/\|/venv/\|/.git/' > ws-files-and-dirs.txt

findcanalisé à greppeut - être mauvaise forme, mais il semblait que la façon la plus directe d'utiliser un filtre regex niée.

La commande suivante inclut uniquement les fichiers dans la sortie de find et a pris sensiblement plus de temps.

% find Workspace/ -type f | grep -v '/vendor\|/node_modules/\|Workspace/sources/\|/venv/\|/.git/' > ws-files-only.txt

J'ai écrit du code pour tester les performances de ces deux commandes (avec dashet tcsh, juste pour exclure tout effet que le shell pourrait avoir, même s'il ne devrait pas y en avoir). Les tcshrésultats ont été omis car ils sont essentiellement les mêmes.

Les résultats que j'ai obtenus montrent une pénalité de 10% -type f

Voici la sortie du programme indiquant le temps nécessaire pour exécuter 1000 itérations de diverses commandes.

% perl tester.pl
/bin/sh -c find Workspace/ >/dev/null
82.986582

/bin/sh -c find Workspace/ | grep -v '/vendor\|/node_modules/\|Workspace/sources/\|/venv/\|/.git/' > /dev/null
90.313318

/bin/sh -c find Workspace/ -type f >/dev/null
102.882118

/bin/sh -c find Workspace/ -type f | grep -v '/vendor\|/node_modules/\|Workspace/sources/\|/venv/\|/.git/' > /dev/null

109.872865

Testé avec

% find --version
find (GNU findutils) 4.4.2
Copyright (C) 2007 Free Software Foundation, Inc.

Sur Ubuntu 15.10

Voici le script Perl que j'ai utilisé pour l'analyse comparative

#!/usr/bin/env perl
use strict;
use warnings;
use Time::HiRes qw[gettimeofday tv_interval];

my $max_iterations = 1000;

my $find_everything_no_grep = <<'EOF';
find Workspace/ >/dev/null
EOF

my $find_everything = <<'EOF';
find Workspace/ | grep -v '/vendor\|/node_modules/\|Workspace/sources/\|/venv/\|/.git/' > /dev/null
EOF

my $find_just_file_no_grep = <<'EOF';
find Workspace/ -type f >/dev/null
EOF

my $find_just_file = <<'EOF';
find Workspace/ -type f | grep -v '/vendor\|/node_modules/\|Workspace/sources/\|/venv/\|/.git/' > /dev/null
EOF

my @finds = ($find_everything_no_grep, $find_everything,
    $find_just_file_no_grep, $find_just_file);

sub time_command {
    my @args = @_;
    my $start = [gettimeofday()];
    for my $x (1 .. $max_iterations) {
        system(@args);
    }
    return tv_interval($start);
}

for my $shell (["/bin/sh", '-c']) {
    for my $command (@finds) {
        print "@$shell $command";
        printf "%s\n\n", time_command(@$shell, $command);
    }
}
Gregory Nisbet
la source
2
Il semble qu'il findfaudrait de toute façon vérifier si un chemin donné correspond à un fichier ou un répertoire afin de parcourir récursivement le contenu des répertoires. - il devrait vérifier s'il s'agit d'un répertoire, il ne devrait pas vérifier s'il s'agit d'un fichier. Il existe d'autres types d'entrées: canaux nommés, liens symboliques, blocs périphériques spéciaux, sockets ... Donc, même s'il a peut-être déjà vérifié si c'est un répertoire, cela ne signifie pas qu'il sait s'il s'agit d'un fichier normal.
RealSkeptic
la recherche de busybox, appliquée à un répertoire aléatoire avec 4,3k dirs et 2,8k fichiers s'exécutent en même temps avec -type fet sans lui. Mais au début, le noyau Linux l'a chargé dans le cache et la toute première découverte a été plus lente.
1
Ma première supposition était que l' -type foption provoquait l' findappel stat()ou fstat()ou quoi que ce soit afin de savoir si le nom du fichier correspondait à un fichier, un répertoire, un lien symbolique, etc. J'ai fait un stracesur un find . et un find . -type fet la trace était presque identique, ne différant que par les write()appels contenant des noms de répertoire. Donc, je ne sais pas, mais je veux connaître la réponse.
Bruce Ediger
1
Pas vraiment une réponse à votre question, mais il y a une timecommande intégrée pour voir combien de temps une commande prend pour s'exécuter, vous n'aviez pas vraiment besoin d'écrire un script personnalisé pour tester.
Elronnd

Réponses:

16

GNU find a une optimisation qui peut être appliquée find .mais pas find . -type f: s'il sait qu'aucune des entrées restantes dans un répertoire n'est un répertoire, alors il ne prend pas la peine de déterminer le type de fichier (avec l' statappel système) à moins que l'un des les critères de recherche l'exigent. L'appel statpeut prendre un temps mesurable car les informations se trouvent généralement dans l'inode, dans un emplacement séparé sur le disque, plutôt que dans le répertoire conteneur.

Comment le sait-il? Parce que le nombre de liens sur un répertoire indique le nombre de sous-répertoires qu'il possède. Sur les systèmes de fichiers Unix typiques, le nombre de liens d'un répertoire est de 2 plus le nombre de répertoires: un pour l'entrée du répertoire dans son parent, un pour l' .entrée et un pour l' ..entrée dans chaque sous-répertoire.

L' -noleafoption indique de findne pas appliquer cette optimisation. Ceci est utile si findest invoqué sur un système de fichiers où le nombre de liens de répertoire ne suit pas la convention Unix.

Gilles 'SO- arrête d'être méchant'
la source
Est-ce toujours pertinent? En regardant la findsource, il utilise simplement les appels fts_open()et fts_read().
RealSkeptic
@RealSkeptic Est-ce que cela a changé dans les versions récentes? Je n'ai pas vérifié la source, mais expérimentalement, la version 4.4.2 dans Debian stable optimise les statappels quand elle n'en a pas besoin en raison du nombre de liens de répertoire, et l' -noleafoption est documentée dans le manuel.
Gilles 'SO- arrête d'être méchant'
Il optimise statmême dans la fts...version - il passe le drapeau approprié pour cela à l' fts_openappel. Mais ce que je ne suis pas sûr est toujours pertinent, c'est la vérification du nombre de liens. Il vérifie à la place si l'enregistrement fts retourné possède l'un des drapeaux "répertoire". Il se peut que fts_readlui - même vérifie les liens pour définir cet indicateur, mais findne le fait pas. Vous pouvez voir si votre version repose sur ftsen appelant find --version.
RealSkeptic
@Gilles, Serait findthéoriquement capable de déterminer quand toutes les entrées d'un répertoire sont également des répertoires et d'utiliser cette information?
Gregory Nisbet
@GregoryNisbet En théorie oui, mais le code source (que j'ai maintenant vérifié) ne fait pas cela, probablement parce que c'est un cas beaucoup plus rare.
Gilles 'SO- arrête d'être méchant'