使用管道和此处的字符串的资源使用情况

使用管道和此处的字符串的资源使用情况

我们可以使用以下两个得到相同的结果bash

echo 'foo' | cat

cat <<< 'foo'

我的问题是,就使用的资源而言,这两者之间有什么区别,哪一个更好?

我的想法是,在使用管道时,我们使用额外的进程echo和管道,而这里的字符串仅使用文件描述符cat

答案1

该管道是在内核文件系统中打开的文件,不能作为磁盘上的常规文件进行访问。它仅自动缓冲到一定大小,并最终在满时阻塞。与源自块设备的文件不同,管道的行为非常类似于字符设备,因此通常不支持lseek()并且从它们读取的数据无法像读取常规文件那样再次读取。

这里的字符串是在已安装的文件系统中创建的常规文件。 shell 创建文件并保留其描述符,同时立即删除其唯一的文件系统链接(所以删除它)在向文件写入/读取字节之前。内核将维护该文件所需的空间,直到所有进程释放该文件的所有描述符。如果从这样的描述符中读取的子级有能力这样做,则可以使用以下命令将其倒回lseek()并再次阅读。

在这两种情况下,标记<<<|代表文件描述符,而不一定是文件本身。您可以通过执行以下操作来更好地了解正在发生的情况:

readlink /dev/fd/1 | cat

...或者...

ls -l <<<'' /dev/fd/*

这两个文件之间最显着的区别是,here-string/doc 几乎是一次性事件 - shell 在向子文件提供读取描述符之前将所有数据写入其中。另一方面,shell 在适当的描述符上打开管道,并分叉子进程来管理管道的描述符 - 因此它被写入/读取同时在两端。

然而,这些区别只是一般来说真的。据我所知(实际上并没有那么远)几乎每个处理此处文档重定向<<<的字符串简写的shell 都是如此<<,只有一个例外yash。不过yashbusyboxdash、 和其他ash变体确实倾向于使用管道支持此处文档,因此在这些 shell 中,两者之间确实没有什么区别。

好的——有两个例外。现在我正在考虑它,ksh93实际上根本没有为 做管道,而是使用套接字处理整个业务 - 尽管它确实像大多数其他人一样|为 做删除的临时文件。<<<*更重要的是,它只是将管道的各个部分放在一个子shell环境这是 POSIX 的一种委婉说法至少它的作用就像一个子shell,所以甚至不做叉子。

事实是@PSkocik 的基准(这非常有用)这里的结果可能会有很大差异许多原因,其中大部分与实现相关。对于此处文档设置,最大的因素是目标${TMPDIR}文件系统类型和当前缓存配置/可用性,以及要写入的数据量。对于管道来说,它将是 shell 进程本身的大小,因为副本是为所需的 fork 制作的。这样bash就是糟糕的在管道设置时(包括$(命令)替换)- 因为它很大而且非常慢,但用ksh93它几乎没有任何区别。

这是另一个小 shell 片段,用于演示 shell 如何为管道拆分子 shell:

pipe_who(){ echo "$$"; sh -c 'echo "$PPID"'; }
pipe_who
pipe_who | { pipe_who | cat /dev/fd/3 -; } 3<&0

32059  #bash's pid
32059  #sh's ppid
32059  #1st subshell's $$
32111  #1st subshell sh's ppid
32059  #2cd subshell's $$
32114  #2cd subshell sh's ppid

pipe_who()管道调用报告的内容与当前 shell 中运行的报告之间的差异是由于(子 shell在扩展时)声明父 shell 的 pid 的指定行为造成的。$$尽管bash子 shell 肯定是单独的进程,但$$特殊的 shell 参数并不是此信息的可靠来源。尽管如此,子 shell 的子shshell 并没有拒绝准确报告其$PPID.

答案2

基准测试无可替代:

pskocik@ProBook:~ 
$ time (for((i=0;i<1000;i++)); do cat<<< foo >/dev/null; done  )

real    0m2.080s
user    0m0.738s
sys 0m1.439s
pskocik@ProBook:~ 
$ time (for((i=0;i<1000;i++)); do echo foo |cat >/dev/null; done  )

real    0m4.432s
user    0m2.095s
sys 0m3.927s
$ time (for((i=0;i<1000;i++)); do cat <(echo foo) >/dev/null; done  )
real    0m3.380s
user    0m1.121s
sys 0m3.423s

对于大量数据:

TENMEG=$(ruby -e 'puts "A"*(10*1024*1024)')
pskocik@ProBook:~ 
$ time (for((i=0;i<100;i++)); do echo "$TENMEG" |cat >/dev/null; done  )

real    0m42.327s
user    0m38.591s
sys 0m4.226s
pskocik@ProBook:~ 
$ time (for((i=0;i<100;i++)); do cat<<< "$TENMEG" >/dev/null; done  )

real    1m26.946s
user    1m23.116s
sys 0m3.681s
pskocik@ProBook:~ 

$ time (for((i=0;i<100;i++)); do cat <(echo "$TENMEG") >/dev/null; done  )

real    0m43.910s
user    0m40.178s
sys 0m4.119s

管道版本的安装成本似乎更高,但最终效率更高。

答案3

正如您已经注意到的,管道创建一个子流程,同时这里的字符串才不是。这在某些情况下可能很重要。

比较这些:

尽管:

grep $USER /etc/passwd | IFS=: read user x1 uid gid x2 home shell 
echo $user $uid $gid

不显示任何内容。

IFS=: read user x1 uid gid x2 home shell <<<$(grep $USER /etc/passwd)
echo $user $uid $gid

显示:

myuser 1000 1000

相关内容