На самом деле все наоборот; команда 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.