我有一个 1 TB 的文件。我想从字节 12345678901 读取到字节 19876543212 并将其放在具有 100 MB RAM 的计算机上的标准输出上。
我可以轻松地编写一个 perl 脚本来执行此操作。 sysread 提供 700 MB/s(这很好),但 syswrite 只能提供 30 MB/s。我想要更高效的东西,最好是每个 Unix 系统都安装的东西,并且可以以 1 GB/s 的速度提供。
我的第一个想法是:
dd if=1tb skip=12345678901 bs=1 count=$((19876543212-12345678901))
但这效率不高。
编辑:
我不知道我是如何测量 syswrite 错误的。这可提供 3.5 GB/s:
perl -e 'sysseek(STDIN,shift,0) || die; $left = shift; \
while($read = sysread(STDIN,$buf, ($left > 32768 ? 32768 : $left))){ \
$left -= $read; syswrite(STDOUT,$buf);
}' 12345678901 $((19876543212-12345678901)) < bigfile
并避免yes | dd bs=1024k count=10 | wc
噩梦。
答案1
由于块大小较小,因此速度很慢。使用最新的 GNU dd
(coreutils v8.16 +),最简单的方法是使用skip_bytes
和count_bytes
选项:
in_file=1tb
start=12345678901
end=19876543212
block_size=4096
copy_size=$(( $end - $start ))
dd if="$in_file" iflag=skip_bytes,count_bytes,fullblock bs="$block_size" \
skip="$start" count="$copy_size"
更新
fullblock
上面添加的选项@吉尔斯回答。起初我以为这可能是暗示的count_bytes
,但事实并非如此。
提到的问题是下面的一个潜在问题,如果dd
读/写调用因任何原因中断,则数据将丢失。在大多数情况下这不太可能(因为我们是从文件而不是管道中读取,所以几率会有所降低)。
使用dd
不带skip_bytes
andcount_bytes
选项的 a 会更困难:
in_file=1tb
start=12345678901
end=19876543212
block_size=4096
copy_full_size=$(( $end - $start ))
copy1_size=$(( $block_size - ($start % $block_size) ))
copy2_start=$(( $start + $copy1_size ))
copy2_skip=$(( $copy2_start / $block_size ))
copy2_blocks=$(( ($end - $copy2_start) / $block_size ))
copy3_start=$(( ($copy2_skip + $copy2_blocks) * $block_size ))
copy3_size=$(( $end - $copy3_start ))
{
dd if="$in_file" bs=1 skip="$start" count="$copy1_size"
dd if="$in_file" bs="$block_size" skip="$copy2_skip" count="$copy2_blocks"
dd if="$in_file" bs=1 skip="$copy3_start" count="$copy3_size"
}
您还可以尝试不同的块大小,但收益不会非常显着。看 -有没有办法确定 dd 的 bs 参数的最佳值?
答案2
bs=1
告诉dd
我们一次读取和写入一个字节。每个read
and调用都会产生开销write
,这使得速度变慢。使用更大的块大小以获得良好的性能。
当你复制整个文件时,至少在 Linux 下,我发现cp
并且cat
比dd
,即使您指定较大的块大小。
要仅复制文件的一部分,您可以通过管道传输tail
到head
.这需要 GNU coreutils 或其他一些必须head -c
复制指定数量字节的实现(tail -c
在 POSIX 中有,但head -c
不是)。 Linux 上的快速基准测试表明这比 慢dd
,大概是因为管道的原因。
tail -c $((2345678901+1)) | head -c $((19876543212-2345678901))
问题dd
在于它不可靠:它可以复制部分数据。据我所知,dd
读取和写入常规文件时是安全的 - 请参阅dd什么时候适合复制数据? (或者,什么时候 read() 和 write() 是部分的)- 但只要不被信号中断即可。使用 GNU coreutils,您可以使用该fullblock
标志,但这不可移植。
另一个问题dd
是很难找到有效的块计数,因为跳过的字节数和传输的字节数都需要是块大小的倍数。您可以使用多个调用dd
:一个用于复制第一个部分块,一个用于复制大部分对齐块,一个用于复制最后一个部分块 - 请参阅格雷姆的回答获取 shell 片段。但不要忘记,当您运行脚本时,除非您使用该fullblock
标志,否则您需要祈祷它dd
会复制所有数据。dd
如果副本不完整,则返回非零状态,因此很容易检测到错误,但没有实际的方法来修复它。
POSIX 在 shell 级别没有提供更好的东西。我的建议是编写一个小型的专用 C 程序(具体取决于您实现的内容,您可以将其称为dd_done_right
或tail_head
或mini-busybox
)。
答案3
和dd
:
dd if=1tb skip=12345678901 count=$((19876543212-12345678901)) bs=1M iflags=skip_bytes,count_bytes
或者使用losetup
:
losetup --find --show --offset 12345678901 --sizelimit $((19876543212-12345678901))
然后dd
,,cat
...循环设备。
答案4
您可以这样做:
i=$(((t=19876543212)-(h=12345678901)))
{ dd count=0 skip=1 bs="$h"
dd count="$((i/(b=64*1024)-1))" bs="$b"
dd count=1 bs="$((i%b))"
} <infile >outfile
这就是真正需要的一切——不需要更多。首先,几乎dd count=0 skip=1 bs=$block_size1
可以lseek()
立即超过常规文件输入。没有机会丢失数据或者无论有什么其他不实说法,您都可以直接寻找您想要的起始位置。因为文件描述符是由 shell 拥有的,而dd
's 只是继承它,所以它们会影响其光标位置,因此您可以逐步执行。它确实非常简单——而且没有比 更适合这项任务的标准工具了dd
。
它使用 64k 块大小,这通常是理想的。与普遍的看法相反,更大的块大小并不会使dd
工作速度更快。另一方面,微小的缓冲区也不好。dd
需要在系统调用中同步其时间,这样就不需要等待将数据复制到内存并再次复制出来,也不需要等待系统调用。因此,您希望它花费足够的时间,以便下一个read()
不必等待最后一个,但也不要太多,以免您缓冲的大小超过所需的大小。
所以第一个dd
跳到开始位置。这需要零时间。此时您可以调用您喜欢的任何其他程序来读取其标准输入,它将直接从您所需的字节偏移处开始读取。我叫另一个人dd
来读((interval / blocksize) -1)
计算到标准输出的块数。
最后需要做的就是复制模数(如果有的话)之前的除法运算。就是这样。
顺便说一句,当人们在没有证据的情况下在脸上陈述事实时,不要相信它。是的,可以dd
进行短读(尽管从健康的块设备读取时不可能发生这种情况 - 因此得名)。仅当您没有正确缓冲dd
从块设备以外读取的流时,这种情况才有可能发生。例如:
cat data | dd bs="$num" ### incorrect
cat data | dd ibs="$PIPE_MAX" obs="$buf_size" ### correct
在这两种情况下都dd
需要复制全部的数据。第一种情况是有可能的(虽然不太可能cat
)一些dd
复制出来的输出块将等于“$num”字节,因为dd
是规范的仅有的当在命令行上特别请求缓冲区时,可以缓冲任何内容。bs=
代表一个最大限度块大小因为目的ofdd
是实时I/O。
在第二个示例中,我明确指定输出块大小和dd
缓冲区读取,直到可以进行完整写入。这不会影响count=
哪个基于输入块,但为此你只需要另一个dd
.否则向您提供的任何错误信息都应被忽略。