为什么是 `tac 文件 | grep foo'(管道)比'grep foo <

为什么是 `tac 文件 | grep foo'(管道)比'grep foo <

这个问题的动机是“反向抓取”,关于从下往上 grep 一个巨大的文件。

@混沌 说:

tac file | grep whatever

或者更有效一点:

grep whatever < <(tac file)

@vinc17 说:

应该< <(tac filename)和管道一样快

其他用户也有许多有趣的评论。

我的问题:

  • |和 和有什么区别< <()
  • 为什么一个比另一个快?
  • 哪个真的更快?
  • 为什么没人建议xargs

答案1

该构造<(tac file)导致壳:

  • 创建一个具有名称的管道
    • 在 Linux 和 SysV 等具有 的系统上/dev/fd,使用常规管道,并将/dev/fd/<the-file-descriptor-of-the-pipe>其用作名称。
    • 在其他系统上,使用命名管道,这需要在磁盘上创建实际的文件条目。
  • 启动命令tac file并将其连接到管道的一端。
  • 将命令行上的整个结构替换为管道的名称。

替换后命令行变为:

grep whatever < /tmp/whatever-name-the-shell-used-for-the-named-pipe

然后grep执行,它读取其标准输入(即管道),读取它,并在其中搜索其第一个参数。

所以最终的结果是一样的......

tac file | grep whatever

...因为启动了相同的两个程序并且仍然使用管道来连接它们。但<( ... )构建过程更加复杂,因为它涉及更多步骤,并且可能涉及临时文件(命名管道)。

<( ... )构造是一个扩展,在标准 POSIX bourne shell 或不支持/dev/fd或命名管道的平台上不可用。仅出于这个原因,由于正在考虑的两种替代方案在功能上完全相同,因此更便携的command | other-command形式是更好的选择。

由于额外的卷积,构建<( ... )应该会更慢,但这只是在启动阶段,我不认为差异很容易测量。

笔记:在 Linux SysV 平台上,< ( ... )不使用命名管道,而是使用常规管道。常规管道(实际上是所有文件描述符)可以通过特殊命名来引用,/dev/fd/<file-descriptor-number因此 shell 使用该名称作为管道的名称。通过这种方式,它可以避免在真实文件系统中创建具有真实临时文件名的真实命名管道。尽管/dev/fd技巧是最初出现在 中时用于实现此功能的技巧ksh,但它是一种优化:在不支持此功能的平台上,将如上所述使用实际文件系统中的常规命名管道。

另请注意:将语法描述为<<( ... )具有误导性。事实上它是<( ... ),它被替换为管道的名称,然后作为<整个事物前缀的另一个字符与此语法分开,并且它是用于从文件重定向输入的常规众所周知的语法。

答案2

| 有什么区别和 <<()?

他们之间有一个区别:

  • |导致每个命令在单独的子 shell 中运行。

  • <()运行该命令,该命令在后台替换。

对于接下来的两个问题,我们将做一些strace

pipe:

$ strace -fc bash -c 'tac /usr/share/dict/american-english | grep qwerty'
$ time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00    0.008120        2707         3         1 wait4
  0.00    0.000000           0       352           read
  0.00    0.000000           0       229           write
  0.00    0.000000           0        20         2 open
  0.00    0.000000           0        29         2 close
  0.00    0.000000           0        40        17 stat
  0.00    0.000000           0        19           fstat
  0.00    0.000000           0       117           lseek
  0.00    0.000000           0        38           mmap
  0.00    0.000000           0        18           mprotect
  0.00    0.000000           0         6           munmap
  0.00    0.000000           0        25           brk
  0.00    0.000000           0        22           rt_sigaction
  0.00    0.000000           0        18           rt_sigprocmask
  0.00    0.000000           0         1           rt_sigreturn
  0.00    0.000000           0         3         2 ioctl
  0.00    0.000000           0        24        12 access
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         2           dup2
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         2           clone
  0.00    0.000000           0         3           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0        13           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         3           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00    0.008120                  1034        37 total

Process Substitution:

$ strace -fc bash -c 'grep qwerty < <(tac /usr/share/dict/american-english)'
$ time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99.14    0.016001        4000         4         2 wait4
  0.46    0.000075           0       229           write
  0.24    0.000038           0       341           read
  0.16    0.000026           1        24           brk
  0.00    0.000000           0        21         2 open
  0.00    0.000000           0        27           close
  0.00    0.000000           0        40        17 stat
  0.00    0.000000           0        19           fstat
  0.00    0.000000           0       117           lseek
  0.00    0.000000           0        38           mmap
  0.00    0.000000           0        18           mprotect
  0.00    0.000000           0         6           munmap
  0.00    0.000000           0        35           rt_sigaction
  0.00    0.000000           0        24           rt_sigprocmask
  0.00    0.000000           0         2           rt_sigreturn
  0.00    0.000000           0         3         2 ioctl
  0.00    0.000000           0        24        12 access
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         3           dup2
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         3           clone
  0.00    0.000000           0         3           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1         1 fcntl
  0.00    0.000000           0         2           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0        13           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         3           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00    0.016140                  1046        37 total

为什么有些东西比其他东西更快?

什么才是真正更快的呢?

您可以看到,比本例process substitution慢,因为它使用了更多的系统调用。pipe两者都花费大量时间等待子进程,但process substitution使用更多的wait4()系统调用,并且每次调用使用的时间都比pipe.

为什么没有人建议 xargs ?

我认为xargs这里没有什么帮助,这不是它的工作。

更新

正如 @Gilles 的建议,我使用更大的文件(从/dev/urandom.这表明pipe确实比 快process substitution

pipe:

$ strace -fc bash -c 'tac sample.txt | grep qwerty'
$ time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 81.15    8.284959     2761653         3         1 wait4
 17.89    1.825959           2    780959           read
  0.91    0.092708           0    524286           write
  0.05    0.005364           0    262146           lseek
  0.00    0.000000           0        20         2 open
  0.00    0.000000           0        29         2 close
  0.00    0.000000           0        40        17 stat
  0.00    0.000000           0        19           fstat
  0.00    0.000000           0        38           mmap
  0.00    0.000000           0        18           mprotect
  0.00    0.000000           0         6           munmap
  0.00    0.000000           0        25           brk
  0.00    0.000000           0        22           rt_sigaction
  0.00    0.000000           0        18           rt_sigprocmask
  0.00    0.000000           0         1           rt_sigreturn
  0.00    0.000000           0         3         2 ioctl
  0.00    0.000000           0        24        12 access
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         2           dup2
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         2           clone
  0.00    0.000000           0         3           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0        13           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         3           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00   10.208990               1567727        37 total

process substitution:

$ strace -fc bash -c 'grep qwerty < <(tac sample.txt)'
$ time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99.51   13.912869     3478217         4         2 wait4
  0.38    0.053373           0    655269           read
  0.09    0.013084           0    524286           write
  0.02    0.002454           0    262146           lseek
  0.00    0.000030           1        38           mmap
  0.00    0.000024           1        24        12 access
  0.00    0.000000           0        21         2 open
  0.00    0.000000           0        27           close
  0.00    0.000000           0        40        17 stat
  0.00    0.000000           0        19           fstat
  0.00    0.000000           0        18           mprotect
  0.00    0.000000           0         6           munmap
  0.00    0.000000           0        24           brk
  0.00    0.000000           0        35           rt_sigaction
  0.00    0.000000           0        24           rt_sigprocmask
  0.00    0.000000           0         2           rt_sigreturn
  0.00    0.000000           0         3         2 ioctl
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         3           dup2
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         3           clone
  0.00    0.000000           0         3           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1         1 fcntl
  0.00    0.000000           0         2           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0        13           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         3           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00   13.981834               1442060        37 total

答案3

我曾是不是能够复制显示的结果库恩勒姆。即使对于 2GB 的文件,在 MacOS Mojave 上的 Bash 5 中,我也看到进程替换和管道之间的时间非常相似。这对我来说很有意义,因为调用所涉及的开销将是最小的与对 2GB 文件的调用的实际处理相比,因此使用进程替换与管道的一次迭代将取决于随机性/首先运行哪个命令来缓存文件内容。

曾是能够复制研究结果在这个问题中这表明进程替换比管道更快拨打这些电话的次数超过数千次。

这是我运行的命令和输出:

管道.sh:

shopt -s lastpipe
for i in {1..5000}; do
    echo foo bar |
    while read; do
        echo $REPLY >/dev/null
    done
done

proc-sub.sh:

for i in {1..5000}; do
    while read; do
        echo $REPLY >/dev/null
    done < <(echo foo bar)
done

管道无最后管道.sh:

for i in {1..5000}; do
    echo foo bar |
    while read; do
        echo $REPLY >/dev/null
    done
done

测试:

time ./proc-sub.sh

real    0m9.505s
user    0m1.875s
sys     0m10.705s

time ./pipe.sh

real    0m14.036s
user    0m4.583s
sys     0m14.193s

time ./pipe-no-lastpipe.sh

real    0m16.696s
user    0m3.055s
sys     0m18.057s

相关内容