问题

问题

问题

当我在 *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/utility-to-buffer-an-unbounded-amount-of-data-in-a-pipeline

为了方便测试,我在下面提供了一个测试脚本,并附上了一些我自己的实验的注释。代码清单后面会详细说明。运行前请确保您已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

这里,生产者每 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] [       <=>                                                 ]

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

dd obs=32M status=none | \
    dd obs=32M status=none| \
    dd obs=32M status=none| \
    dd obs=32M status=none

这是通过将所需的 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 + 后台进程,结果相同。

PS。请注意,在将 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.??>

答案1

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

警告

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

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

如果您的某个外部 USB 驱动器损坏且速度缓慢,并且需要极高的 IO 强度才能进行读/写,那么这种情况会尤其有害。

例子

我基本上用尽了所有 DD 选项的组合。单个 DD 似乎无法完成这项任务,因为它无法执行异步 IO。否则,在 DD 缓冲区链中,必须先填充最大的块,然后它才能开始像 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

添加pv以查看速度。这里,xz仅在从 A 读取 1GB 数据后才开始(除非数据少于 1GB,否则将完成)。类似地,仅在从 xz 输出 1GB 数据后才开始向 B 写入磁盘。此代码在tar和之间提供 2GB 缓冲区,在和写入xz之间提供 2GB 。最后的并不是必需的,但我发现较大的(>64M)块大小可以提供更好的平均写入速度,尤其是在 USB 硬盘上。我想如果驱动器 B 正在使用中(未经证实),它也会创建更少的碎片。xzbs=512M

示例 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

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

示例 3。感谢 Kamil Maciorowski,现在我的 中有以下内容.zshrc

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

现在,如果您需要 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

相关内容