Почему `найти. -типа f` займет больше времени, чем `find .`?

15

Кажется, findчто в любом случае придется проверить, соответствует ли данный путь файлу или каталогу, чтобы рекурсивно просмотреть содержимое каталогов.

Вот некоторая мотивация и то, что я сделал на местном уровне, чтобы убедить себя, что на find . -type fсамом деле медленнее, чем find .. Я еще не копался в GNU найти исходный код.

Поэтому я создаю резервные копии некоторых файлов в моем $HOME/Workspaceкаталоге и исключаю файлы, которые являются либо зависимостями моих проектов, либо файлами контроля версий.

Итак, я запустил следующую команду, которая выполнялась быстро

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

findЭто grepможет быть плохая форма, но это кажется самым прямым способом использования отрицательного фильтра регулярных выражений.

Следующая команда включает в себя только файлы в выводе find и заняла заметно больше времени.

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

Я написал некоторый код для проверки производительности этих двух команд (с dashи tcsh, просто чтобы исключить любые эффекты, которые может иметь оболочка, даже если их не должно быть). Эти tcshрезультаты были опущены , поскольку они по существу то же самое.

Результаты, которые я получил, показали около 10% снижения производительности за -type f

Вот выходные данные программы, показывающие количество времени, необходимое для выполнения 1000 итераций различных команд.

% 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

Протестировано с

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

На Ubuntu 15.10

Вот скрипт Perl, который я использовал для бенчмаркинга

#!/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
Кажется, findчто в любом случае придется проверить, соответствует ли данный путь файлу или каталогу, чтобы рекурсивно просмотреть содержимое каталогов. - он должен проверить, является ли он каталогом, он не должен проверять, является ли он файлом. Существуют и другие типы записей: именованные каналы, символические ссылки, блочные специальные устройства, сокеты ... Так что, возможно, он уже выполнил проверку, чтобы определить, является ли это каталогом, но это не значит, что он знает, является ли он обычным файлом.
RealSkeptic
Функция busybox find, применяемая к произвольному каталогу с 4,3 тыс. каталогов и 2,8 тыс. файлов, запускается одновременно -type fи без него. Но вначале ядро ​​Linux загрузило его в кеш, и самая первая находка была медленнее.
1
Мое первое предположение состояло в том, что -type fпараметр вызвал findвызов stat()или fstat()что-то еще, чтобы выяснить, соответствует ли имя файла файлу, каталогу, символической ссылке и т. Д. И т. Д. Я сделал straceon find . и a, find . -type fи трассировка была почти идентична, отличается только write()вызовами, в которых были имена каталогов. Итак, я не знаю, но я хочу знать ответ.
Брюс Эдигер
1
Не совсем ответ на ваш вопрос, но есть timeвстроенная команда, чтобы увидеть, сколько времени занимает выполнение команды, вам не нужно было писать собственный скрипт для тестирования.
Elronnd

Ответы:

16

GNU find имеет оптимизацию, которую можно применять, find .но не к ней find . -type f: если он знает, что ни одна из оставшихся записей в каталоге не является каталогами, он не будет определять тип файла (с помощью statсистемного вызова), если только один из критерии поиска требуют этого. Вызов statможет занять измеримое время, так как информация обычно находится в inode, в отдельном месте на диске, а не в директории, содержащей ее.

Как это узнать? Потому что количество ссылок на каталог указывает, сколько у него подкаталогов. В типичных файловых системах Unix количество ссылок на каталог равно 2 плюс количество каталогов: один для записи каталога в его родительском элементе, один для .записи и один для ..записи в каждом подкаталоге.

-noleafОпция говорит findне применять эту оптимизацию. Это полезно, если findвызывается в некоторой файловой системе, где количество ссылок на каталоги не соответствует соглашению Unix.

Жиль "ТАК - прекрати быть злым"
источник
Это все еще уместно? Глядя на findисточник, он просто использует fts_open()и fts_read()вызывает в настоящее время.
RealSkeptic
@RealSkeptic Изменилось ли это в последних версиях? Я не проверял источник, но экспериментально версия 4.4.2 в стабильной версии Debian оптимизирует statвызовы, когда они не нужны, из-за количества ссылок в каталогах, и эта -noleafопция описана в руководстве.
Жиль "ТАК - перестань быть злым"
Он оптимизирует statдаже в fts...версии - он передает соответствующий флаг для fts_openвызова. Но то, что я не уверен, все еще уместно - проверка с количеством ссылок. Вместо этого он проверяет, имеет ли возвращенная запись fts один из флагов «каталога». Может быть, он fts_readсам проверяет ссылки, чтобы установить этот флаг, но findне делает этого. Вы можете увидеть, зависит ли ваша версия fts, позвонив find --version.
RealSkeptic
@ Жиль, findтеоретически сможет определить, когда все все записи в каталоге тоже каталоги, и использовать эту информацию?
Грегори Нисбет
@GregoryNisbet В теории да, но исходный код (я сейчас проверял) этого не делает, по-видимому, потому что это гораздо более редкий случай.
Жиль "ТАК - перестань быть злым"