Почему команда «найти | grep 'filename' ”намного медленнее, чем“ find 'filename ””?

10

Я пробовал обе команды, и команда find | grep 'filename' во много раз медленнее, чем простая find 'filename' команда.

Каково было бы правильное объяснение этого поведения?

yoyo_fun
источник
2
Вы перечисляете каждый файл с помощью команды find и затем передаете данные в grep для обработки. При использовании find самостоятельно, вы пропускаете шаг передачи каждого перечисленного файла в grep для анализа вывода. Поэтому это будет быстрее.
Раман Sailopal
В каком смысле медленнее? Команды занимают различное количество времени, чтобы закончить?
Кусалананда
1
Я не могу воспроизвести это локально. Во всяком случае, time find "$HOME" -name '.profile'отчеты дольше, чем time find "$HOME" | grep -F '.profile'. (17 с против 12 с).
Кусалананда
2
@JenniferAnderson Я запускал оба раза. 17 и 12 секунд являются средними. И да, grepвариация будет совпадать в любом месте findрезультата, тогда как совпадение с find -nameбудет совпадать только (в данном случае).
Кусалананда
2
Да, find filename будет быстро . Я вроде предположил, что это опечатка и что имел в виду ОП find -name filename. С find filename, только filenameбудет рассмотрено (и ничего больше).
Кусалананда

Ответы:

11

(Я предполагаю, что GNU findздесь)

Используя только

find filename

будет быстрым, потому что он просто вернет filenameили имена внутри, filenameесли это каталог, или ошибку, если этого имени не существует в текущем каталоге. Это очень быстрая операция, похожая на ls filename(но рекурсивная, если filenameэто каталог).

По сравнению,

find | grep filename

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

Я предполагаю , что то , что было на самом деле предназначены было

find . -type f -name 'filename'

Это будет выглядеть filenameкак имя обычного файла в любом месте текущего каталога или ниже.

Это будет так же быстро (или сравнительно быстро) find | grep filename, но grepрешение будет соответствовать filenameполному пути каждого найденного имени, аналогично тому, что -path '*filename*'будет с find.


Путаница возникает из-за недопонимания того, как findработает.

Утилита берет несколько путей и возвращает все имена под этими путями.

Затем вы можете ограничить возвращаемые имена, используя различные тесты, которые могут воздействовать на имя файла, путь, метку времени, размер файла, тип файла и т. Д.

Когда ты говоришь

find a b c

Вы просите findперечислить каждое имя, доступное под тремя путями a, bи c. Если это имена обычных файлов в текущем каталоге, они будут возвращены. Если какое-либо из них окажется именем каталога, оно будет возвращено вместе со всеми последующими именами в этом каталоге.

Когда я делаю

find . -type f -name 'filename'

Это создает список всех имен в текущем каталоге ( .) и ниже. Затем он ограничивает имена имен обычных файлов, то есть не каталогов и т -type f. Д., С. Тогда есть дальнейшее ограничение на имена, которые соответствуют filenameиспользованию -name 'filename'. Строка filenameможет быть шаблоном подстановки имени файла, например *.txt(просто не забудьте процитировать его!).

Пример:

Кажется, следующее «находит» файл, вызываемый .profileв моем домашнем каталоге:

$ pwd
/home/kk
$ find .profile
.profile

Но на самом деле, он просто возвращает все имена по пути .profile(есть только одно имя, и это из этого файла).

Затем я cdподнимаюсь на один уровень и пытаюсь снова:

$ cd ..
$ pwd
/home
$ find .profile
find: .profile: No such file or directory

Теперь findкоманда не может найти ни один вызванный путь .profile.

Однако, если я получу его, чтобы посмотреть на текущий каталог, а затем ограничить возвращаемые имена только.profile , он также найдет его оттуда:

$ pwd
/home
$ find . -name '.profile'
./kk/.profile
Кусалананда
источник
1
find filenameбудет возвращать только filenameесли filenameне было типа каталога (или был директории типа, но не было ни сам вход)
Stéphane Chazelas
2

Нетехническое объяснение: искать Джека в толпе быстрее, чем искать всех в толпе и исключать все из рассмотрения, кроме Джека.

С Ренальдс
источник
Проблема в том, что ОП ожидает, что Джек будет единственным человеком в толпе. Если это так, им повезло. find jackбудет перечислять, jackесли это файл называется jack, или все имена в каталоге, если это каталог. Это неправильное понимание того, как findработает.
Кусалананда
1

Я еще не понял проблему, но могу дать еще несколько идей.

Как и в случае с Кусаланандой, find | grepвызов в моей системе явно быстрее, что не имеет особого смысла. Сначала я предположил некоторую проблему буферизации; эта запись в консоль замедляет время до следующего системного вызова для чтения следующего имени файла. Запись в канал очень быстрая: около 40 МБ / с даже для 32-байтовых записей (на моей довольно медленной системе; 300 МБ / с для блока размером 1 МБ). Таким образом, я предположил, что он findможет читать из файловой системы быстрее при записи в канал (или файл), так что две операции: чтение путей к файлам и запись в консоль могут выполняться параллельно (что findкак однопотоковый процесс не может выполнять самостоятельно).

Это findвина

Сравнивая два звонка

:> time find "$HOME"/ -name '*.txt' >/dev/null

real    0m0.965s
user    0m0.532s
sys     0m0.423s

а также

:> time find "$HOME"/ >/dev/null

real    0m0.653s
user    0m0.242s
sys     0m0.405s

показывает, что findделает что-то невероятно глупое (что бы это ни было). Оказывается, он совершенно некомпетентен в исполнении -name '*.txt'.

Может зависеть от отношения ввода / вывода

Вы можете подумать, что find -nameпобеждает, если писать очень мало. Но это просто становится более смущающим для find. Он проигрывает, даже если нечего писать против файлов 200К (13М данных канала) для grep:

time find /usr -name lwevhewoivhol

findможет быть так быстро, как grep, хотя

Оказывается, что findглупость с nameне распространяется на другие тесты. Вместо этого используйте регулярное выражение, и проблема исчезла:

:> time find "$HOME"/ -regex '\.txt$' >/dev/null     

real    0m0.679s
user    0m0.264s
sys     0m0.410s

Я думаю, это можно считать ошибкой. Кто-нибудь готов подать отчет об ошибке? Моя версия найти (GNU findutils) 4.6.0

Хауке Лагинг
источник
Насколько повторяемы ваши сроки? Если вы сначала сделали -nameтест, то, возможно, он был медленнее из-за того, что содержимое каталога не кэшировалось. (При тестировании -nameи -regexя считаю , они берут примерно в то же время, по крайней мере , один раз эффект кэша было принято во внимание Конечно , это может быть просто другая версия. find...)
psmears
@psmears Конечно, я делал эти тесты несколько раз. Проблема кеширования упоминалась даже в комментариях к вопросу перед первым ответом. Моя findверсия найти (GNU findutils) 4.6.0
Hauke ​​Laging
Почему удивительно, что добавление -name '*.txt'замедляется find? Он должен сделать дополнительную работу, проверяя каждое имя файла.
Бармар
@ Barmar С одной стороны, эту дополнительную работу можно выполнить очень быстро. С другой стороны, эта дополнительная работа спасает другую работу. findдолжен писать меньше данных. И запись в канал - намного более медленная операция.
Хауке Лагинг
Запись на диск очень медленная, запись в канал не так уж плоха, он просто копируется в буфер ядра. Обратите внимание на то, что в первом тесте вы пишете больше, чтобы /dev/nullкак-то использовать меньше системного времени.
Бармар
0

Обратите внимание : я предполагаю, что вы имеете в виду find . -name filename(в противном случае вы ищете разные вещи; на find filenameсамом деле изучаете путь, называемый filename , который может почти не содержать файлов, поэтому очень быстро завершается).


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

Так что, когда вы просите , findчтобы найти файл, имя которого требуется только проверка, findбудет просить для этого файла, и этот файл только для базовой файловой системы, которая будет читать очень мало страниц из хранилища массы. Поэтому, если файловая система того стоит, эта операция будет выполняться намного быстрее, чем обход всего дерева для получения всех записей.

Когда вы спрашиваете о простой, findно это именно то, что вы делаете, вы пересекаете все дерево, читая. Каждый. Один. Вступление. С большими каталогами это может быть проблемой (именно по этой причине несколько программ, которым нужно хранить много файлов на диске, будут создавать «деревья каталогов» глубиной в два или три компонента: таким образом, каждый отдельный лист должен содержать меньше файлы).

LSerni
источник
-2

Предположим, что файл / john / paul / george / ringo / beatles существует, и файл, который вы ищете, называется 'stone'

find / stones

find сравнивает «битлы» с «камнями» и отбрасывает их, когда «s» и «b» не совпадают.

find / | grep stones

В этом случае find передает '/ john / paul / george / ringo / beatles' в grep, и grep должен будет пройти весь путь, прежде чем определить, соответствует ли он.

поэтому grep делает гораздо больше работы, поэтому это занимает больше времени

параноик
источник
1
Вы дали это попробовать?
Хауке Лагинг
3
Стоимость сравнения строк (чрезвычайно простая и дешевая) полностью уменьшается стоимостью IO (или просто системным вызовом, если она кэшируется) при поиске в каталоге.
Мат
grep - это не сравнение строк, это сравнение с регулярными выражениями, что означает, что он должен пройти через всю строку, пока не найдет совпадение или не достигнет конца. Поиск в каталоге одинаков, несмотря ни на что.
Параноик
@Paranoid Хм, о какой версии находки ты говоришь? Похоже, это не что-то вроде находки, к которой я привык в Debian.
труба