6

Насколько я понимаю, две следующие команды примерно выполняют одно и то же:

Команда 1:

find -name "filename.xml" -exec grep someString {} \;

Команда 2:

grep -r --include=filename.xml someString .

Тем не менее, при синхронизации их после разогрева в том же контексте, первый был примерно в 3 раза быстрее, чем второй (что-то вроде 4 секунды против 12 секунд).

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

Так почему же такая большая разница в производительности этих двух командных строк?

2 ответа2

3

На самом деле все наоборот; команда grep, как правило, более эффективна.

Я поработаю над снимком дерева Portage из Gentoo, который доступен для всех, если вы хотите попробовать.

 $ time find /usr/portage/sys-apps/ -name '*.ebuild' -exec grep DEPEND {} \; > /dev/null

real    0m1.184s
user    0m0.033s
sys     0m0.130s

 $ time grep -r --include '*.ebuild' DEPEND /usr/portage/sys-apps/ > /dev/null

real    0m0.017s
user    0m0.007s
sys     0m0.010s

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

 $ (strace find /usr/portage/sys-apps/ -name '*.ebuild' -exec grep DEPEND {} \; > /dev/null) |& sed 's/[({].*//g' | sort | uniq -c | sort -r | head -n 10
   3574 fcntl
   1597 close
    794 newfstatat
    794 getdents
    689 wait4
    689 clone
    689 --- SIGCHLD 
    404 fstat
    397 openat
     20 mmap

 $ (strace grep -r --include '*.ebuild' DEPEND /usr/portage/sys-apps/ > /dev/null) |& sed 's/[({].*//g' | sort | uniq -c | sort -r | head -n 10
   2779 fcntl
   1493 close
   1382 read
   1096 fstat
   1087 openat
    794 getdents
    792 newfstatat
    691 ioctl
    689 lseek
     25 write

А также посмотрите на звонки, которые были длинными:

 $ (strace -T find /usr/portage/sys-apps/ -name '*.ebuild' -exec grep DEPEND {} \; > /dev/null) |& sed 's/\(.*\)<\(.*\)>/\2 \1/g' | sort -nk1r | head -n10
exit_group(0)                           = ?
0.001884 wait4(29725, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 29725 
0.001879 wait4(29475, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 29475 
0.001813 wait4(29430, [{WIFEXITED(s) && WEXITSTATUS(s) == 1}], 0, NULL) = 29430 
0.001812 wait4(30089, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 30089 
0.001807 wait4(29722, [{WIFEXITED(s) && WEXITSTATUS(s) == 1}], 0, NULL) = 29722 
0.001795 wait4(29645, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 29645 
0.001794 wait4(29848, [{WIFEXITED(s) && WEXITSTATUS(s) == 1}], 0, NULL) = 29848 
0.001759 wait4(30032, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 30032 
0.001754 wait4(30093, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 30093

 $ (strace -T grep -r --include '*.ebuild' DEPEND /usr/portage/sys-apps/ > /dev/null) |& 
exit_group(0)                           = ?
0.002336 fcntl(3, F_SETFD, FD_CLOEXEC)           = 0 
0.000460 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\30`C6\0\0\0"..., 832) = 832 
0.000313 close(3)                                = 0 
0.000295 execve("/bin/grep", ["grep", "-r", "--include", "*.ebuild", "DEPEND", "/usr/portage/sys-apps/"], [/* 75 vars */]) = 0 
0.000276 fcntl(3, F_SETFD, FD_CLOEXEC)           = 0 
0.000265 getdents(3, /* 244 entries */, 32768)   = 7856 
0.000233 fstat(3, {st_mode=S_IFREG|0644, st_size=826, ...}) = 0 
0.000162 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 
0.000137 lseek(3, 1402, 0x4 /* SEEK_??? */)      = -1 ENXIO (No such device or address) 

Довольно интересно, вы видите в этом выводе продолжительности, что find ждет много, тогда как grep делает некоторые вещи, необходимые для запуска и остановки процесса. Ожидание вызовов занимает более 0,001 с, тогда как поиск вызовов уменьшается до стабильных ~ 0,0002 с.

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

Есть случаи, когда он не страдает ; вы можете получить достаточно маленький набор файлов, чтобы не было слишком больших затрат на запуск нескольких процессов grep, у вас также может быть очень медленный диск, который не учитывает затраты на запуск нового процесса, и, вероятно, есть и другие причины. Хотя, сравнивая скорость, мы часто смотрим на то, насколько хорошо масштабируется тот или иной подход, а не на специальные угловые случаи.

В вашем случае вы упомянули, что « именно поэтому я чувствую, что способ, которым" grep "посещает дерево каталогов, является неэффективным по сравнению с" найти ". », Это действительно может иметь место; как вы можете видеть, было сделано 1382 вызовов read, тогда как find не делает этого, это делает grep более интенсивным для вас.

TL; DR: Чтобы понять, почему ваши тайминги неэффективны, попробуйте снова выполнить этот анализ и точно определить проблему в вашем случае, чтобы вы знали, почему ваши конкретные данные и задачи не эффективны в grep; Вы узнаете, как может вести себя grep в вашем угловом корпусе ...

Таким образом, как и предполагали другие, вы захотите убедиться, что он не вызывает grep для каждого файла, что можно сделать, заменив \; + ближе к концу.

 $ time find /usr/portage/sys-apps/ -name '*.ebuild' -exec grep DEPEND {} + > /dev/null

real    0m0.027s
user    0m0.010s
sys     0m0.013s

Как видите, 0,027 с приближается к 0,017 с; разница в основном связана с тем, что он все еще должен вызывать и find, и grep, а не только один grep. Или, как показано в комментариях, в некоторых системах знак + позволяет улучшить работу с grep.

3

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

Это может звучать как пустое предложение, но в этом случае, например, grep выполняется один раз для каждого файла в примере поиска. Это не мудрая производительность. Если вы замените закрывающий аргумент find на «+» вместо «\;», grep запустится только один раз для всех найденных файлов.

Чтобы с уверенностью ответить в таком случае, нужно сравнить соответствующие части исходного кода для grep и найти, что быстрее при сопоставлении (поиске) имен файлов. Честно говоря, это выше моих навыков.

Интуитивно я бы сказал, что find оптимизирован для поиска файлов в каталогах, а grep оптимизирован для поиска строк в файлах. Кроме того, опция --include должна работать как с прописными, так и со строчными файлами, тогда как `-name

редактировать: (мои выводы были неверны)

Некоторые основные исследования в папке с документами ~ 35 тысяч файлов:

$ strace find . -name "moo" -exec grep a {} \+ 2>&1 |grep ^open |wc -l
4448

$ strace grep -r --include=moo  a . 2>&1 | grep ^open | wc -l
2289

Комбинация поиска открывает намного больше файлов. Это предполагает противоположность ваших выводов. Я сделал некоторые базовые сроки (как Том Wijsman).

DIR=imagemagick-6.7.8.7
$ findhtml $DIR |& top10    $ grephtml $DIR |& top10
  1617 mmap2                  316 read
  1176 fstat64                173 close
  1176 close                  164 fstat64
   735 open                   157 openat
   608 read                   148 ioctl
   588 mprotect                63 fcntl64
   441 brk                     25 getdents64
   294 munmap                  16 fstatat64
   294 ioctl                   11 mmap2
   147 write                    5 write
time: Real 0m2.0s           time: Real 0m0.3s

Я обнаружил, что поиск find указывает на /usr /lib /locale /locale-archive, но я не совсем уверен, каковы последствия.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .