为什么要查找。类型的f所花的时间要比查找更长的时间?


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

我编写了一些代码来测试这两个命令的性能(使用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

经过测试

% 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查找,应用于具有4,3k个目录和2,8k个文件的随机目录,无论有无目录都可以同时运行-type f。但是,起初,Linux内核将其加载到缓存中,但是第一次发现的速度较慢。

1
我的第一个猜测是,该-type f选项导致find调用stat()fstat()或任何以找出是否该文件名对应一个文件,目录,符号链接,等等等等我做了strace一个find . find . -type f和跟踪几乎相同,仅在write()其中具有目录名称的调用中有所不同。所以,我不知道,但是我想知道答案。
Bruce Ediger

1
并不是真正回答您的问题,但是有一个time内置命令可以查看命令执行所需的时间,您实际上不需要编写自定义脚本来进行测试。
Elronnd

Answers:


16

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

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

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


这仍然有意义吗?从find源头上看,它现在仅使用fts_open()fts_read()调用。
RealSkeptic

@RealSkeptic在最新版本中有此更改吗?我没有检查源,但是实验上,stat由于目录链接数的原因,Debian stable中的4.4.2版本确实不需要调用时会优化调用,该-noleaf选项在手册中有说明。
吉尔斯(Gilles)'所以

stat即使在fts...版本中,它也进行了优化-将适当的标志传递给fts_open调用。但是我不确定仍然与链接数量有关。而是检查返回的fts记录是否具有“目录”标志之一。可能是fts_read本身检查了设置该标志的链接,但find没有进行检查。您可以fts通过调用来查看您的版本是否依赖find --version
RealSkeptic

@Gilles,从find理论上讲,能够确定目录中的所有条目也是目录时并使用该信息吗?
格雷戈里·尼斯贝

@GregoryNisbet从理论上讲是的,但是源代码(我已经检查过)不会那样做,大概是因为这种情况很少见。
吉尔斯(Gilles)'所以
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.