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


16

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

echo 'foo' | cat

cat <<< 'foo'

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

我的想法是,在使用管道时,我们使用了额外的进程echo和管道,而在此字符串中,仅文件描述符与一起使用cat

Answers:


17

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

这里的字符串是在已挂载文件系统中创建的常规文件。Shell创建文件并保留其描述符,同时在它向文件中写入/读取一个字节之前立即删除其唯一的文件系统链接(并因此删除了它)。内核将保持文件所需的空间,直到所有进程释放该文件的所有描述符为止。如果从此类描述符读取的子级有能力这样做,则可以将其倒退lseek()并读取。

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

readlink /dev/fd/1 | cat

...要么...

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

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

但是,这些区别通常是正确的。据我所知(实际上还不是全部),几乎所有处理shell <<<此处字符串缩写的shell都适用<<,唯一的例外是yashyashbusyboxdash,和其他ash变异体都倾向于管道回到这里的文档,虽然,所以在那些炮弹真的是两个毕竟差别很小。

好的-两个例外。现在,我正在考虑它,ksh93实际上并没有为它做任何管道|,而是通过套接字处理整个业务,尽管它确实<<<*像大多数其他工具一样处理了已删除的tmp文件。而且,它仅将管道的各个部分放在subshel​​l环境中,这是一种POSIX委婉说法,至少它的作用类似于subshel​​l,所以甚至不做分叉。

事实是,@ 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()调用报告与当前外壳中一次运行的报告之间的差异是由于(子外壳)指定的行为,即在$$扩展时要求父外壳的pid 。尽管bash子shell绝对是单独的进程,但是$$特殊的shell参数并不是此信息的可靠来源。子sh外壳的子外壳仍然不会拒绝准确报告其子外壳$PPID


非常有帮助。内核文件系统,有名称吗?这是否意味着它存在于内核空间中?
utlamn

2
@utlamn-实际上,是的-简单地是pipefs。这一切都在内核-但(除东西一样FUSE)等是所有的文件I / O
mikeserv

10

基准测试无可替代:

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

看起来管道版本的安装成本较高,但最终效率更高。


@mikeserv没错。我添加了一个包含大量数据的基准。
PSkocik

2
echo foo >/dev/shm/1;cat /dev/shm/1 >/dev/null似乎在两种情况下都很快...
user23013

@ user23013这很有道理。我不明白为什么无论是echo "$longstring"<<<"$longstring"将进行调整以提高效率,并与短字符串,效率没有多大意义呢。
PSkocik 2015年

有趣的是,在我的情况下(在Ubuntu 14.04上,英特尔四核i7)cat <(echo foo) >/dev/nullecho foo | cat >/dev/null
pabouk

1
@Prem是的,这将是一种更好的方法,但更好的方法是完全不用担心这一点,并使用正确的工具来完成工作。没有理由认为heredocs将进行性能调整。
PSkocik 2015年
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.