Perché `trova. -type f` impiega più tempo di `find .`?


15

Sembra che finddovrebbe verificare se un determinato percorso corrisponde comunque a un file o una directory al fine di percorrere ricorsivamente il contenuto delle directory.

Ecco un po 'di motivazione e cosa ho fatto localmente per convincermi che find . -type fè davvero più lento di find .. Non ho ancora scavato nella GNU per trovare il codice sorgente.

Quindi eseguo il backup di alcuni dei file nella mia $HOME/Workspacedirectory ed escludo i file che dipendono dai miei progetti o dai file di controllo della versione.

Quindi ho eseguito il seguente comando che è stato eseguito rapidamente

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

findreindirizzato greppotrebbe essere una cattiva forma, ma sembrava il modo più diretto per utilizzare un filtro regex negato.

Il comando seguente include solo i file nell'output di find e ha impiegato notevolmente più tempo.

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

Ho scritto del codice per testare le prestazioni di questi due comandi (con dashe tcsh, solo per escludere qualsiasi effetto che la shell potrebbe avere, anche se non dovrebbe essercene). I tcshrisultati sono stati omessi perché sono essenzialmente gli stessi.

I risultati ottenuti hanno mostrato una penalità di prestazione del 10% per -type f

Ecco l'output del programma che mostra la quantità di tempo impiegata per eseguire 1000 iterazioni di vari comandi.

% 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

Testato con

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

Su Ubuntu 15.10

Ecco lo script perl che ho usato per il benchmarking

#!/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);
    }
}

2
Sembra che finddovrebbe verificare se un determinato percorso corrisponde comunque a un file o una directory al fine di percorrere ricorsivamente il contenuto delle directory. - dovrebbe verificare se si tratta di una directory, non dovrebbe verificare se si tratta di un file. Esistono altri tipi di voci: named pipe, collegamenti simbolici, blocchi di dispositivi speciali, socket ... Quindi, sebbene possa aver già fatto il controllo per vedere se si tratta di una directory, ciò non significa che sappia se si tratta di un file normale.
RealSkeptic,

busybox find, applicato a una directory casuale con 4,3k dir e 2,8k file viene eseguito contemporaneamente con -type fe senza di essa. Ma per la prima volta il kernel Linux lo ha caricato nella cache e la prima scoperta è stata più lenta.

1
La mia prima ipotesi è stata che l' -type fopzione ha causato la findchiamata stat()o fstat()qualsiasi altra cosa per scoprire se il nome del file corrispondeva a un file, una directory, un collegamento simbolico, ecc. Ecc. Ho fatto un stracesu a find . e a find . -type fe la traccia era quasi identica, differendo solo nelle write()chiamate che contenevano nomi di directory. Quindi non lo so, ma voglio sapere la risposta.
Bruce Ediger,

1
Non è davvero una risposta alla tua domanda, ma c'è un timecomando integrato per vedere quanto tempo richiede l'esecuzione di un comando, non è necessario scrivere uno script personalizzato per testare.
Elronnd,

Risposte:


16

GNU find ha un'ottimizzazione a cui può essere applicato find . ma non a find . -type f: se sa che nessuna delle voci rimanenti in una directory sono directory, allora non si preoccupa di determinare il tipo di file (con la statchiamata di sistema) a meno che uno dei i criteri di ricerca lo richiedono. La chiamata statpuò richiedere del tempo misurabile poiché le informazioni sono in genere nell'inode, in una posizione separata sul disco, piuttosto che nella directory di contenimento.

Come lo sa? Perché il conteggio dei collegamenti in una directory indica quante sottodirectory ha. Sui filesystem Unix tipici, il conteggio dei collegamenti di una directory è 2 più il numero di directory: una per la voce della directory nel suo genitore, una per la .voce e una per la ..voce in ciascuna sottodirectory.

L' -noleafopzione dice di findnon applicare questa ottimizzazione. Ciò è utile se findviene invocato su alcuni filesystem in cui i conteggi dei collegamenti alle directory non seguono la convenzione Unix.


È ancora pertinente? Guardando la findfonte, usa semplicemente fts_open()e fts_read()chiama al giorno d'oggi.
RealSkeptic,

@RealSkeptic È cambiato nelle ultime versioni? Non ho controllato l'origine, ma sperimentalmente la versione 4.4.2 in Debian stable ottimizza le statchiamate quando non ne ha bisogno a causa del conteggio dei collegamenti alla directory e l' -noleafopzione è documentata nel manuale.
Gilles 'SO- smetti di essere malvagio'

Ottimizza statanche nella fts...versione - passa il flag appropriato per quello alla fts_openchiamata. Ma ciò che non sono sicuro è ancora pertinente è il controllo con il numero di collegamenti. Controlla invece se il record fts restituito ha uno dei flag "directory". Può darsi che esso fts_readcontrolli i collegamenti per impostare quella bandiera, mafind non lo fa. Puoi vedere se la tua versione si basa su ftschiamando find --version.
RealSkeptic,

@Gilles, findteoricamente sarebbe in grado di determinare quando tutte le voci in una directory sono anch'esse directory e utilizzare tali informazioni?
Gregory Nisbet,

@GregoryNisbet In teoria sì, ma il codice sorgente (che ora ho controllato) non lo fa, presumibilmente perché è un caso molto più raro.
Gilles 'SO- smetti di essere malvagio'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.