为什么`find . -type f` 比 `find .` 花费的时间更长?

Gre*_*bet 15 performance find gnu

似乎find必须检查给定的路径是否与文件或目录相对应,以便递归地遍历目录的内容。

这是一些动机以及我在本地所做的事情,以说服自己find . -type f确实比find .. 我还没有深入研究 GNU 查找源代码。

所以我正在备份我$HOME/Workspace目录中的一些文件,并排除属于我的项目或版本控制文件的依赖项的文件。

所以我运行了以下快速执行的命令

% find Workspace/ | grep -v '/vendor\|/node_modules/\|Workspace/sources/\|/venv/\|/.git/' > ws-files-and-dirs.txt
Run Code Online (Sandbox Code Playgroud)

find管道传输到grep可能是不好的形式,但它似乎是使用否定正则表达式过滤器的最直接方式。

以下命令仅包含 find 输出中的文件,并且花费的时间明显更长。

% find Workspace/ -type f | grep -v '/vendor\|/node_modules/\|Workspace/sources/\|/venv/\|/.git/' > ws-files-only.txt
Run Code Online (Sandbox Code Playgroud)

我编写了一些代码来测试这两个命令的性能(使用dashtcsh,只是为了排除 shell 可能产生的任何影响,即使不应该有任何影响)。的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
Run Code Online (Sandbox Code Playgroud)

测试过

% find --version
find (GNU findutils) 4.4.2
Copyright (C) 2007 Free Software Foundation, Inc.
Run Code Online (Sandbox Code Playgroud)

在 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Gil*_*il' 16

GNU find 有一个优化,可以应用于find .但不能应用于find . -type f:如果它知道目录中剩余的条目都不是目录,那么它不会费心确定文件类型(使用stat系统调用),除非其中之一搜索条件需要它。调用stat可能需要可测量的时间,因为信息通常位于 inode 中,位于磁盘上的单独位置,而不是包含目录中。

它怎么知道?因为目录上的链接数表示它有多少个子目录。在典型的 Unix 文件系统上,一个目录的链接数是 2 加上目录数:一个用于目录在其父目录中的条目,一个用于.条目,一个用于..每个子目录中的条目。

-noleaf选项告诉find不要应用此优化。如果find在目录链接计数不遵循 Unix 约定的某些文件系统上调用,这将很有用。