使用 dd 作为管道缓冲区的正确方法(如果有)?

Car*_*ong 5 linux bash command-line dd pipe

问题

当我在 *NIX 中寻找管道缓冲工具时,我看到了使用buffermbuffer或的建议pv。然而,前两者并不总是在发行版的官方存储库中(例如 Arch),而pv(从 1.6.0 开始)有一个错误阻止了此功能。在其他几个问题中,我看到提到dd用作缓冲区,我想探索它,因为dd它总是存在。然而,没有一个足够详细以具有真正的意义,所以在这里我要求一种“正确”的使用方式。

提到的问题dd包括https://unix.stackexchange.com/questions/345072/can-dd-be-used-to-add-a-buffer-to-a-pipehttps://unix.stackexchange.com/questions /21918/管道中无限制数据量缓冲区的实用程序

为了便于测试,我在下面提供了一个测试脚本,以及一些关于我自己的实验的评论。详细信息将在代码清单后解释。运行前请确保您已pv安装且内存至少256M!

#!/bin/sh

producer() {
    while [ 1 ]; do
    dd if=/dev/zero bs=64M count=1 iflag=fullblock status=none
    sleep 4
    done
}

buffer() {
    # Works, but long
    # Must at least fill 32M before consumer starts
    # So, must choose small obs and chain more to look
    # more like a proper "buffer"
    dd obs=32M status=none | \
        dd obs=32M status=none| \
        dd obs=32M status=none| \
        dd obs=32M status=none
    # Doesn't work, producer rate limited
    #dd bs=128M status=none 
    # Doesn't work, producer must fill buffer, then
    # must wait until buffer is empty
    #dd obs=128M status=none 
    # Doesn't work, producer rate limited
    #dd ibs=128M status=none 
    # Doesn't work, producer must fill buffer, then
    # must wait until buffer is empty
    #dd bs=128M status=none iflag=fullblock
}

consumer() {
    pv --rate-limit 1M -q | dd of=/dev/null status=none
}

producer | pv -cN produce | buffer | pv -cN consume | consumer
Run Code Online (Sandbox Code Playgroud)

这里,生产者每 4 秒产生 64MB 的数据,具有 128MB 的缓冲区,而消费者以恒定的 1MB/s 速率消费。当然,这意味着缓冲区很快就会溢出,但这是为了清楚地显示效果。理想情况下,在缓冲区填满之前(在第三次生产时),我们应该看到恒定的 1MB/s 消耗,以及每次产生 64MB 数据的突发生产。“正确”的输出如下所示:

  produce:  128MiB 0:00:07 [   0 B/s] [  <=>                                                       ]
  consume: 7.25MiB 0:00:07 [1.01MiB/s] [       <=>                                                 ]
Run Code Online (Sandbox Code Playgroud)

这里,工作解决方案如下所示:

dd obs=32M status=none | \
    dd obs=32M status=none| \
    dd obs=32M status=none| \
    dd obs=32M status=none
Run Code Online (Sandbox Code Playgroud)

这是通过将所需的 128MB 缓冲区分成 4 个块来构建的。是的,在数据​​传递到下一级之前,每个块都必须填满,但由于 32MB 小于 64MB 突发,因此它适用于此测试,就好像它是真正的缓冲区一样。现在,存在一些问题。

  1. 在实际应用中,我们没有瞬时突发的数据,因此块需要很小,但不能太小。这意味着将会有一长串dd命令
  2. 如果在达到 32MB 标记之前遇到 EOF 怎么办?那块会丢失吗?我测试dd if=test.txt| dd obs=1M | dd obs=1M | dd of=test2.txt并比较了结果。事实证明这不是问题。因此,使用它进行备份不会损坏数据。
  3. 它会产生多少开销?
  4. 是否有更优雅的方法通过巧妙地安排参数来实现相同的目的?

脚本中还包含一些其他尝试,但它们不起作用,如评论中所述。我尝试过使用 FIFO + 后台进程,这会产生相同的结果。

附言。正如您所知,在将 A 备份到 B 时,缓冲管道非常有用,尤其是当 A 是具有寻道时间的 HDD 时。所以我会做这样的事情:

tar cSpf - <path> -C <root path> | <a large buffer> | <some parallel compressor> \
| <small buffer if compressor is generally slow and B have seek time> \
| dd bs=<several GB if B is not an SSD> iflag=fullblock oflag=direct of=<archive.tar.??>
Run Code Online (Sandbox Code Playgroud)

Car*_*ong 1

我正在输入我自己的答案。它可能不是最好的,但还可以。

警告

这是经过多次测试才写在前面的。

不要链接太多 DD 进行缓冲,否则所有 CPU 核心都可能阻塞 IO,并且即使您还有大量内存,您的计算机也会死机!

如果您有一些损坏的慢速外部 USB 驱动器,并且还需要荒谬的 IO 强度来进行读/写,则尤其有毒。

例子

我基本上用尽了DD选项的所有组合。单个 DD 似乎不可能完成此任务,因为它无法执行异步 IO。否则,在 DD-buffer 链中,必须先填充最大的块,然后才能开始像 FIFO 一样工作。因此,如果您不关心填充管道时的初始延迟...两个 dd 的工作链。我希望其他人可以提供更优雅的解决方案,但这里有一个示例用法。

示例 1:将所有文件从严重碎片化的 HDD A(响应时间抖动)压缩到严重碎片化的 HDD B(抖动),并行使用 XZ 作为压缩算法(慢)(如果您实际使用计算机,则会出现抖动)(免责声明:我是凭自己的想法写的,所以一些小细节可能是错误的。使用风险由您自己承担):

tar -cpSf - -C /mnt/A . | \
  dd obs=1024M | dd obs=1024M | \
  xz -T 0 -c | \
  dd obs=1024M | dd obs=1024M | \
  dd bs=512M iflag=fullblock of=/mnt/B/A.tar.xz
Run Code Online (Sandbox Code Playgroud)

添加pv以查看速度。这里,xz只有从A读取1GB数据后才开始(除非小于1GB,则结束)。同样,只有在 xz 输出 1GB 数据后,才会开始向 B 写入磁盘。tar此代码在和 之间提供 2GB 缓冲区xz,在 和 之间提供 2GBxz写入缓冲区。最后的bs=512M并不是真正必要的,但我发现大的(> 64M)块大小可以提供更好的平均写入速度,尤其是在 USB 硬盘上。我想如果驱动器 B 正在使用(未确认),它也会产生更少的碎片。

示例2。目标:将一个巨大的文件从碎片严重的磁盘 A 复制到碎片严重的磁盘 B。

dd if=/mnt/A/file obs=<half of cache you want> | dd bs=<half of cache> iflag=fullblock oflag=direct of=/mnt/B/file
Run Code Online (Sandbox Code Playgroud)

这是我能找到的最简单的形式之一。如果文件足够大,则用于填充缓存的初始时间应该可以忽略不计。同时,它异步读取/写入,并希望将足够的写入组合在一起以获得一些顺序性能。不过,我想 SSD 不会关心块大小。

示例3。感谢卡米尔·马乔罗夫斯基 (Kamil Maciorowski),我的 中现在有以下内容.zshrc

buffer() {
    if [ "$2" -gt 0 ] ; then
        dd status=none obs="$1" | buffer "$1" $(($2-1))
    else 
        cat 
    fi
}
Run Code Online (Sandbox Code Playgroud)

现在,如果您需要 3 个 512M 缓冲区块,请链接buffer 512M 3到您的管道中。一般来说,如果您的作业对于吞吐量来说足够大(例如,平均以 100MB/s 的速度复制/压缩 100GB+ 数据),则较小的块除了更快地填充管道之外没有任何优势(这是无关紧要的,因为这个时间很短) 。我观察到,如果放入太多块,CPU 可能会因 IO 过于繁忙而导致该命令冻结整个计算机。

现在,示例 1 变为

tar -cpSf - -C /mnt/A . | \
buffer 1024M 2 | \
xz -T 0 -c | \
buffer 1024M 2 | \
dd bs=512M iflag=fullblock of=/mnt/B/A/tar.xz
Run Code Online (Sandbox Code Playgroud)