【问题标题】:How to stream one file to multiple pipelines efficiently如何有效地将一个文件流式传输到多个管道
【发布时间】:2018-08-14 07:07:12
【问题描述】:

我有一个脚本想要在一个非常大的文件上运行多个程序/管道。示例:

grep "ABC" file > file.filt
md5sum file > file.md5

内核会尝试将文件缓存在 RAM 中,因此如果很快再次读取它,它可能是 RAM 中的副本。但是文件很大,程序运行的速度也大不相同,所以这不太可能有效。为了尽量减少 IO 使用,我想读取一次文件。

我知道使用 tee 和 moreutils 的 pee 复制数据的 2 种方法:

<file tee >(md5sum > file.md5) | grep "ABC" > file.filt
<file pee 'md5sum > file.md5' 'grep "ABC" > file.filt'

还有其他“最佳”方式吗?哪种方法制作的副本最少? >() 或 |-ed 对哪个程序有影响吗?如果一个程序太慢,这些方法中的任何一种都会尝试在 RAM 中缓冲数据吗?它们如何扩展到许多阅读器程序?

【问题讨论】:

  • 在最后两次尝试中,您缺少重定向到file.filt,它应该是&gt; file.filt 对吗?此外,您正在阅读file 一次,您的逻辑看起来还不错!正在努力改进什么?还慢吗?

标签: bash pipe tee


【解决方案1】:

tee(命令)使用fopen 打开每个文件,但在每个文件上设置_IONBF(无缓冲)。 reads 来自标准输入,fwrites 来自每个 FILE*。

pee(命令)popens 每个命令,将每个命令设置为无缓冲,reads 来自标准输入,fwrites 来自每个 FILE*。 popen 使用 pipe(2),其容量为 65536 字节。写入一个完整的缓冲区将被阻塞。 pee 也使用 /bin/sh 来解释命令,但我认为这不会添加任何缓冲/复制。

mkfifo(命令)使用mkfifo(libc),它在下面使用管道,打开文件/管道块直到另一端打开。

bash () 语法 (subst.c:5712) 使用 pipemkfifopipe 如果支持 /dev/fds。它不使用 c fopen 调用,因此不设置缓冲。

因此,所有三个变体(pee、tee >()、mkfifo ...)都应该以相同的行为结束,从标准输入读取并写入管道而不进行缓冲。数据在每次读取(从内核到用户)时复制,然后在每次写入时再次复制(用户返回到内核),我 认为 tees fwrites 不会导致额外的复制层(因为没有缓冲区)。内存使用量最多可增加到 65536 * num_readers + 1 * read_size(如果没有人在阅读)。 tee 先写入标准输出,然后依次写入每个文件/管道。

鉴于这个小便只适用于其他贝壳(鱼!)缺乏等效的 >() 运算符,bash 似乎不需要它。当你有 bash 时我更喜欢 tee,但是当你没有时 pee 很好。 bash

pee 可以通过使用tee 库函数(而不是 fwrite)实现来更改。我认为这会导致以最快的阅读器速度读取输入,并可能填满内核缓冲区。

【讨论】:

    【解决方案2】:

    AFAIK,没有“最好的方法”来实现这一点。但我可以给你另一种方法,更冗长,而不是单行,但可能更清晰,因为每个命令都是用自己的方式编写的。使用命名管道:

    mkfifo tmp1 tmp2
    tee tmp1 > tmp2 < file &
    cat tmp1 | md5sum > file.md5 &
    cat tmp2 | grep "ABC" > file.filt &
    wait
    rm tmp1 tmp2
    
    1. 创建与要运行的命令一样多的名称管道。
    2. tee 到命名管道输入文件(tee 在标准输出中输出其输入,因此姓氏管道必须是重定向),让它在后台运行。
    3. 使用不同的命名管道作为不同命令的输入来运行。让它们在后台运行。
    4. 最后,等待作业完成并移除临时命名管道。

    这种方法的缺点是,当程序的速度变化很大时,它们都会以相同的速度读取文件(限制是缓冲区大小,一旦其中一个管道满了,其他管道就会有也等待),所以如果其中一个是资源匮乏的(比如内存匮乏),则资源将用于所有进程的整个生命周期。

    【讨论】:

    • 我不同意这更清楚,它删除了不为人知的 bash >() 语法,但要求您使用原语来做同样的事情。它没有那么优雅地死去。
    • 你是对的。它做同样的事情,我已经说过这是另一种方法。它不会优雅地死在一个街区上。这只是完成所请求任务的另一种方式。并且避免了数千个字符的行。如果您必须使用相同的输入运行 1000 个进程,您将很容易在单行方法中遇到“行太长”错误。
    • 我认为行长限制适用于 argv + env 对吧?所以pee 肯定会达到这个限制。但是 tee with >() 是许多单独的命令行,你的方法仍然使用tee a b c,所以会达到同样的限制吗?
    • 一方面,len("tee a")tees 以​​避免任何限制:tee a b c &lt; input&amp; tee d e f &lt;a&amp; tee g h i &lt;b&amp; 等等。
    • 是的,级联 T 恤很有意义。但另一方面,“tee >(abc)”由 bash 解释,而不是加载程序,并且 bash 没有长度限制(我可以找到)。 bash 将 "tee >(abc)" 变成 2 个命令 "abc" 和 "tee /dev/fdx"。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-11
    • 2015-02-18
    • 1970-01-01
    • 2017-02-16
    相关资源
    最近更新 更多