¿Por qué `encuentra. -tipo f` lleva más tiempo que `find.

15

Parece findque tendría que verificar si una ruta determinada corresponde a un archivo o directorio de todos modos para recorrer recursivamente el contenido de los directorios.

Aquí hay algo de motivación y lo que he hecho localmente para convencerme de que find . -type frealmente es más lento que find .. Todavía no he profundizado en el código fuente de GNU find.

Así que estoy haciendo una copia de seguridad de algunos de los archivos en mi $HOME/Workspacedirectorio y excluyendo los archivos que son dependencias de mis proyectos o archivos de control de versiones.

Entonces ejecuté el siguiente comando que se ejecutó rápidamente

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

findcanalizado a greppuede ser de mal gusto, pero parecía que el camino más directo para utilizar un filtro de expresiones regulares negada.

El siguiente comando incluye solo archivos en la salida de find y tomó notablemente más tiempo.

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

Escribí un código para probar el rendimiento de estos dos comandos (con dashy tcsh, solo para descartar cualquier efecto que pueda tener el shell, aunque no debería haber ninguno). Los tcshresultados se han omitido porque son esencialmente los mismos.

Los resultados que obtuve mostraron una penalización de rendimiento del 10% por -type f

Aquí está la salida del programa que muestra la cantidad de tiempo necesario para ejecutar 1000 iteraciones de varios comandos.

% 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

Probado con

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

En Ubuntu 15.10

Aquí está el script perl que utilicé para la evaluación comparativa

#!/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
fuente
2
Parece findque tendría que verificar si una ruta determinada corresponde a un archivo o directorio de todos modos para recorrer recursivamente el contenido de los directorios. - tendría que verificar si es un directorio, no tendría que verificar si es un archivo. Existen otros tipos de entradas: canalizaciones con nombre, enlaces simbólicos, dispositivos especiales de bloque, enchufes ... Por lo tanto, aunque ya haya realizado la comprobación para ver si es un directorio, no significa que sepa si es un archivo normal.
RealSkeptic
busybox find, aplicado al directorio aleatorio con 4,3k directorios y 2,8k archivos ejecutados al mismo tiempo con -type fy sin él. Pero al principio, el kernel de Linux lo cargó en caché y el primer hallazgo fue más lento.
1
Mi primera suposición fue que la -type fopción causado finda llamar stat()o fstat()o lo que sea con el fin de averiguar si el nombre de archivo corresponde a un archivo, un directorio, un enlace simbólico, etc, etc hice una straceen una find . y una find . -type fy la traza era casi idéntica, solo difiere en las write()llamadas que tenían nombres de directorio en ellas. Entonces, no sé, pero quiero saber la respuesta.
Bruce Ediger
1
Realmente no es una respuesta a su pregunta, pero hay un timecomando incorporado para ver cuánto tiempo tarda un comando en ejecutarse, realmente no necesitaba escribir un script personalizado para probar.
Elronnd

Respuestas:

16

GNU find tiene una optimización que puede aplicarse find .pero no a find . -type f: si sabe que ninguna de las entradas restantes en un directorio son directorios, entonces no se molesta en determinar el tipo de archivo (con la statllamada al sistema) a menos que uno de los los criterios de búsqueda lo requieren. Llamar statpuede llevar un tiempo medible ya que la información generalmente está en el inodo, en una ubicación separada en el disco, en lugar de en el directorio que lo contiene.

Como lo sabe Debido a que el recuento de enlaces en un directorio indica cuántos subdirectorios tiene. En los sistemas de archivos Unix típicos, el recuento de enlaces de un directorio es 2 más el número de directorios: uno para la entrada del directorio en su padre, uno para la .entrada y uno para la ..entrada en cada subdirectorio.

La -noleafopción le dice que findno aplique esta optimización. Esto es útil si findse invoca en algún sistema de archivos donde los recuentos de enlaces de directorio no siguen la convención de Unix.

Gilles 'SO- deja de ser malvado'
fuente
¿Sigue siendo pertinente? Mirando la findfuente, simplemente usa fts_open()y fts_read()llama hoy en día.
RealSkeptic
@RealSkeptic ¿Ha cambiado esto en versiones recientes? No he comprobado la fuente, pero experimentalmente, la versión 4.4.2 en Debian estable optimiza las statllamadas cuando no las necesita debido al recuento de enlaces del directorio, y la -noleafopción está documentada en el manual.
Gilles 'SO- deja de ser malvado'
Se optimiza statincluso en la fts...versión: pasa la bandera apropiada para eso a la fts_openllamada. Pero lo que no estoy seguro es pertinente es la verificación con el número de enlaces. En su lugar, verifica si el registro fts devuelto tiene uno de los indicadores de "directorio". Es posible que fts_readcompruebe los enlaces para establecer esa bandera, pero findno lo hace. Puede ver si su versión se basa ftsllamando find --version.
RealSkeptic
@Gilles, ¿Teóricamente findpodría determinar cuándo todas las entradas en un directorio son directorios también y usar esa información?
Gregory Nisbet
@GregoryNisbet En teoría sí, pero el código fuente (ahora lo he comprobado) no hace eso, presumiblemente porque es un caso mucho más raro.
Gilles 'SO- deja de ser malvado'