
1 TB のファイルがあります。12345678901 バイト目から 19876543212 バイト目までを読み取り、100 MB の RAM を搭載したマシンの標準出力に表示したいと思います。
これを実行する Perl スクリプトを簡単に作成できます。sysread は 700 MB/秒 (これは問題ありません) を実現しますが、syswrite は 30 MB/秒しか実現できません。もっと効率的なもの、できればすべての Unix システムにインストールされ、1 GB/秒程度の速度を実現できるものが欲しいです。
私の最初のアイデアは次のとおりです。
dd if=1tb skip=12345678901 bs=1 count=$((19876543212-12345678901))
しかし、それは効率的ではありません。
編集:
syswrite の測定を間違えた理由はわかりません。これは 3.5 GB/秒です:
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
(コアユーティリティ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
オプションは上記に従って追加されました@Gilles の回答最初は によって暗示されるかもしれないと思いましたがcount_bytes
、そうではありませんでした。
言及されている問題は、以下の潜在的な問題であり、dd
読み取り/書き込み呼び出しが何らかの理由で中断されると、データが失われます。ほとんどの場合、これは起こりそうにありません (パイプではなくファイルから読み取っているため、可能性はいくらか低くなります)。
およびオプションdd
なしでを使用するのはより困難です。skip_bytes
count_bytes
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
一度に 1 バイトずつ読み取りと書き込みを行うように指示します。呼び出しread
ごとにオーバーヘッドが発生するwrite
ため、処理が遅くなります。適切なパフォーマンスを得るには、ブロック サイズを大きくしてください。
ファイル全体をコピーする場合、少なくともLinuxでは、cp
そしてcat
より速いdd
大きなブロック サイズを指定した場合でも、
ファイルの一部だけをコピーするには、tail
にパイプします。これには、指定されたバイト数をコピーする必要head
がある GNU coreutils またはその他の実装が必要です(は POSIX にありますが、そうではありません)。Linux での簡単なベンチマークでは、おそらくパイプが原因で、よりも遅いことがわかりました。head -c
tail -c
head -c
dd
tail -c $((2345678901+1)) | head -c $((19876543212-2345678901))
問題dd
は信頼性が低い:部分的なデータをコピーできる私の知る限り、dd
通常のファイルを読み書きする場合は安全です。dd はいつデータのコピーに適しているのでしょうか? (または、read() と write() が不完全なのはいつでしょうか)- しかし信号によって中断されない限りGNU coreutils ではfullblock
フラグを使用できますが、これは移植性がありません。
のもう 1 つの問題はdd
、スキップされたバイト数と転送されたバイト数の両方がブロック サイズの倍数である必要があるため、機能するブロック数を見つけるのが難しいことです。 を複数回呼び出すことができます。1dd
回は最初の部分ブロックをコピーし、1 回は整列したブロックの大部分をコピーし、1 回は最後の部分ブロックをコピーします。詳細については、グレアムの答えシェル スニペット用。ただし、スクリプトを実行するときは、フラグを使用しない限り、すべてのデータがコピーされるfullblock
ことを確認する必要があることを忘れないでください。コピーが部分的である場合は 0 以外のステータスが返されるため、エラーを検出するのは簡単ですが、それを修復する実用的な方法はありません。dd
dd
POSIX には、シェル レベルではこれより優れたものはありません。私のアドバイスとしては、小さな専用 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()
欠落データあるいは、それについて他のどんな嘘が言われていようと、希望する開始位置に直接シークすることができます。ファイル記述子はシェルによって所有され、 はdd
それを継承しているだけなので、カーソル位置に影響を与え、段階的に進めることができます。これは本当に非常に簡単で、 以上にこのタスクに適した標準ツールはありませんdd
。
これは、多くの場合理想的な 64k のブロック サイズを使用します。一般に信じられていることとは反対に、ブロック サイズを大きくしてもdd
作業は速くなりません。一方、小さなバッファも良くありません。dd
システム コールの時間を同期して、データをメモリにコピーして再びコピーするのを待つ必要がないようにするだけでなく、システム コールを待つ必要もありません。したがって、次の処理がread()
最後の処理を待たずに済むように十分な時間をかけますが、必要以上に大きなサイズでバッファリングするほど長くはかけないようにする必要があります。
最初はdd
スタート位置までスキップします。ゼロその時点で他の任意のプログラムを呼び出してその標準入力を読み込むと、希望するバイトオフセットから直接読み込みが開始されますdd
。((interval / blocksize) -1)
ブロック数を stdout にカウントします。
最後に必要なのは係数をコピーすることです(もしあれば)前回の分割操作の。これで終わりです。
ところで、証拠もなしに事実を顔だけで述べる人は信じないでください。はい、dd
短い読み物でも可能です(ただし、正常なブロック デバイスから読み取る場合はそのようなことは不可能です。これが名前の由来です)dd
このようなことは、ブロック デバイス以外から読み取られたストリームを正しくバッファリングしない場合にのみ可能です。例:
cat data | dd bs="$num" ### incorrect
cat data | dd ibs="$PIPE_MAX" obs="$buf_size" ### correct
どちらの場合もdd
コピー全てデータの。最初のケースでは、(ただし、 ではありそうにないcat
)コピーされる出力ブロックの一部は、仕様によりdd
「$num」バイトに等しくなります。dd
のみコマンドラインでバッファが明示的に要求されたときに、バッファリングを一切行わないbs=
。最大ブロックサイズは目的はdd
リアルタイム I/O です。
2 番目の例では、出力ブロック サイズを明示的に指定し、dd
完全な書き込みが行えるまで読み取りをバッファします。これは、count=
入力ブロックに基づく には影響しませんが、そのためには別の が必要ですdd
。それ以外の場合に提供される誤った情報は無視してください。