web-dev-qa-db-ja.com

大きなファイルの中央を読む

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悪夢。

20
Ole Tange

ブロックサイズが小さいため、これは低速です。最近のGNU ddcoreutils 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 answer に従って追加しました。最初はcount_bytesによって暗示されるのではないかと思ったのですが、そうではありません。

上記の問題は、以下の潜在的な問題です。dds読み取り/書き込み呼び出しが何らかの理由で中断された場合、データが失われます。ほとんどの場合、これは起こりそうにありません(パイプではなくファイルから読み取るため、オッズはいくらか減少します)。


skip_bytesおよびcount_bytesオプションなしでddを使用することは、より困難です。

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パラメータの最適値を決定する方法はありますか?

22
Graeme

bs=1は、ddに一度に1バイトの読み取りと書き込みを行うように指示します。 readwriteの呼び出しごとにオーバーヘッドが発生するため、これが遅くなります。適切なパフォーマンスを得るには、より大きなブロックサイズを使用します。

ファイル全体をコピーすると、少なくともLinuxでは、 cpcatdd より高速であることがわかりました。大きなブロックサイズを指定します。

ファイルの一部のみをコピーするには、tailheadにパイプします。これには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のもう1つの問題は、スキップされたバイト数と転送されたバイト数の両方がブロックサイズの倍数である必要があるため、機能するブロック数を見つけるのが難しいことです。 ddへの複数の呼び出しを使用できます。1つは最初の部分ブロックをコピーし、1つは整列したブロックの大部分をコピーし、もう1つは最後の部分ブロックをコピーします。詳細は Graeme's answer シェルスニペット。ただし、スクリプトを実行するときには、fullblockフラグを使用していない限り、ddがすべてのデータをコピーすることを祈る必要があることを忘れないでください。コピーが部分的である場合、ddはゼロ以外のステータスを返すため、エラーを検出するのは簡単ですが、実際に修復する方法はありません。

POSIXは、シェルレベルで提供するより良いものはありません。私のアドバイスは、小さな特別な目的のCプログラムを書くことです(実装するものに応じて、dd_done_rightまたはtail_headまたはmini-busybox)。

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))

そしてddcat、...ループデバイス。

4
frostschutz

これはあなたがこれを行う方法です:

_   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は開始位置までスキップします。 zero 時間かかります。その時点で好きな他のプログラムを呼び出してそのstdinを読み取ると、目的のバイトオフセットで直接読み取りを開始できます。別のddを呼び出して、 _((interval / blocksize) -1)_ countブロックをstdoutに読み取ります。

最後に必要なことは、前の除算演算の係数(存在する場合)をコピーすることです。そして、それはそれです。

ところで、人々が証拠なしで自分の顔に事実を述べているとき、それを信じてはいけません。はい、ddが短い読み取りを実行することは可能です(そのようなことは、正常なブロックデバイスから読み取る場合は不可能です-したがって、名前)。このようなことは、ブロックデバイス以外から読み取られたddストリームを正しくバッファリングしない場合にのみ可能です。例えば:

_cat data | dd bs="$num"    ### incorrect
cat data | dd ibs="$PIPE_MAX" obs="$buf_size"   ### correct
_

どちらの場合でも、ddはデータの all をコピーします。最初のケースでは、catではありそうもないことですが、ddがコピーする出力ブロックの一部が「$ num "dd only で指定されているため、コマンドラインでバッファが明示的に要求されたときに、何でもバッファリングするため、バイト数。 _bs=_は、最大ブロックサイズを表します。これは、dd purpose がリアルタイムI/Oであるためです。

2番目の例では、完全な書き込みができるまで、出力ブロックサイズとddバッファーの読み取りを明示的に指定しています。これは入力ブロックに基づく_count=_には影響しませんが、そのためには別のddが必要です。他の方法で与えられた誤報は無視してください。

0
mikeserv