Por que `encontra. -type f` leva mais tempo que `find .`?

15

Parece que findseria necessário verificar se um determinado caminho corresponde a um arquivo ou diretório de qualquer maneira para percorrer recursivamente o conteúdo dos diretórios.

Aqui estão algumas motivações e o que eu fiz localmente para me convencer de que find . -type fé realmente mais lento do que find .. Ainda não procurei no código-fonte do GNU find.

Portanto, estou fazendo backup de alguns arquivos no meu $HOME/Workspacediretório e excluindo arquivos que são dependências dos meus projetos ou arquivos de controle de versão.

Então eu executei o seguinte comando que executou rapidamente

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

findcanalizado greppode ter uma forma incorreta, mas parecia a maneira mais direta de usar um filtro regex negado.

O comando a seguir inclui apenas arquivos na saída de localização e levou muito mais tempo.

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

Eu escrevi um código para testar o desempenho desses dois comandos (com dashe tcsh, apenas para descartar quaisquer efeitos que o shell possa ter, mesmo que não deva haver). Os tcshresultados foram omitidos porque são essencialmente os mesmos.

Os resultados obtidos mostraram uma penalidade de desempenho de 10% para -type f

Aqui está a saída do programa mostrando a quantidade de tempo necessário para executar 1000 iterações de vários 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

Testado com

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

No Ubuntu 15.10

Aqui está o script perl que eu usei para fazer 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);
    }
}
Gregory Nisbet
fonte
2
Parece que findseria necessário verificar se um determinado caminho corresponde a um arquivo ou diretório de qualquer maneira para percorrer recursivamente o conteúdo dos diretórios. - teria que verificar se é um diretório, não precisaria verificar se é um arquivo. Existem outros tipos de entrada: pipes nomeados, links simbólicos, bloquear dispositivos especiais, soquetes ... Portanto, embora já tenha feito a verificação para ver se é um diretório, isso não significa que ele saiba se é um arquivo comum.
RealSkeptic
busybox find, aplicado ao diretório aleatório com 4,3k dirs e 2,8k arquivos executados ao mesmo tempo com -type fe sem ele. Mas, pela primeira vez, o kernel Linux o carregou no cache e a primeira descoberta foi mais lenta.
1
Minha primeira suposição era de que a -type fopção causada findchamar stat()ou fstat()ou qualquer outra coisa, a fim de descobrir se o nome do arquivo corresponde a um arquivo, um diretório, um link simbólico, etc etc. Eu fiz uma straceem uma find . e uma find . -type fe o traço era quase idêntico, diferindo apenas nas write()chamadas que continham nomes de diretório. Então, eu não sei, mas quero saber a resposta.
Bruce Ediger #
1
Não é realmente uma resposta para sua pergunta, mas há um timecomando interno para ver quanto tempo um comando leva para ser executado; você realmente não precisava escrever um script personalizado para testar.
Elronnd

Respostas:

16

O GNU find tem uma otimização que pode ser aplicada, find .mas não a find . -type f: se souber que nenhuma das entradas restantes em um diretório são diretórios, não se preocupa em determinar o tipo de arquivo (com a statchamada do sistema), a menos que um dos critérios de pesquisa exige isso. A chamada statpode levar um tempo mensurável, pois as informações geralmente estão no inode, em um local separado no disco, e não no diretório que o contém.

Como ele sabe? Como a contagem de links em um diretório indica quantos subdiretórios ele possui. Em sistemas de arquivos Unix típicos, a contagem de links de um diretório é 2 mais o número de diretórios: um para a entrada do diretório em seu pai, um para a .entrada e um para a ..entrada em cada subdiretório.

A -noleafopção informa findpara não aplicar essa otimização. Isso é útil se findfor chamado em algum sistema de arquivos em que a contagem de links de diretório não siga a convenção do Unix.

Gilles 'SO- parar de ser mau'
fonte
Isso ainda é pertinente? Olhando a findfonte, ele simplesmente usa as chamadas fts_open()e fts_read()hoje em dia.
RealSkeptic
@RealSkeptic Isso mudou nas versões recentes? Eu não verifiquei a fonte, mas experimentalmente, a versão 4.4.2 no Debian stable otimiza as statchamadas quando não precisa delas devido à contagem de links de diretório, e a -noleafopção está documentada no manual.
Gilles 'SO- stop be evil'
Otimiza statmesmo na fts...versão - passa o sinalizador apropriado para a fts_openchamada. Mas o que não tenho certeza ainda é pertinente é a verificação do número de links. Em vez disso, verifica se o registro fts retornado possui um dos sinalizadores "diretório". Pode ser que fts_readele próprio verifique os links para definir esse sinalizador, mas findnão o faz. Você pode ver se sua versão depende ftsligando find --version.
RealSkeptic #
@Gilles, findteoricamente seria capaz de determinar quando todas as entradas de um diretório também são diretórios e usar essas informações?
Gregory Nisbet
@ GregoryNisbet Em teoria, sim, mas o código-fonte (agora verifiquei) não faz isso, provavelmente porque é um caso muito mais raro.
Gilles 'SO- stop be evil'