大容量ファイルの中間部分を読む

大容量ファイルの中間部分を読む

1TBファイルがあります。バイト 12345678901 からバイト 19876543212 まで読み込み、100 MB RAM のコンピュータの stdout に保存したいと思います。

これを行うには、Perlスクリプトを簡単に作成できます。 sysread は 700 MB/s を提供しますが (良好)、syswrite は 30MB/s だけを提供します。私はもっ​​と効率的なものが欲しいです。可能であれば、すべてのUnixシステムにインストールされ、1GB / sを提供できることを望みます。

私の最初の考えは次のとおりです。

dd if=1tb skip=12345678901 bs=1 count=$((19876543212-12345678901))

しかし、これはそれほど効率的ではありません。

編集する:

syswriteエラーをどのように測定したのかわかりません。これは3.5GB / 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 ddCoretils 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何らかの理由で読み取り/書き込み呼び出しが中断されると、データは失われます。ほとんどの場合、そうではない可能性があります(パイプではなくファイルから読み取られるため、確率が低下します)。


andオプションddなしでaを使用することはより困難です。skip_bytescount_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=1dd一度に1バイトずつ読み書きするように指示します。各read呼び出しにはオーバーヘッドが発生し、write速度が遅くなります。良いパフォーマンスを得るには、より大きなブロックサイズを使用してください。

少なくともLinuxでは、ファイル全体をコピーしたときに次のことがわかりました。cpそしてcatよりdd、より大きなブロックサイズを指定しても同様です。

ファイルの一部だけをtailコピーするにはhead。 Linuxの高速ベンチマークによると、おそらくパイプのために遅いことがわかりました。head -ctail -chead -cdd

tail -c $((2345678901+1)) | head -c $((19876543212-2345678901))

問題dd信頼できません。部分データをコピーできます。。私が知っているdd限り、通常のファイルを読み書きするのは安全です。ddはいつデータのコピーに適していますか? (またはread()とwrite()が部分的な場合)- しかし信号によって妨げられない限り。 GNU coreutils ではフラグを使用できますが、fullblock移植可能ではありません。

もう1つの問題ddは、スキップされたバイト数と送信されたバイト数の両方がブロックサイズの倍数である必要があるため、有効なブロック数を見つけるのが難しいことです。複数の呼び出しを使用できますdd。 1つは最初の部分ブロックをコピーし、1つはソートされたブロックの大部分をコピーし、もう1つは最後の部分ブロックをコピーします。 - 参照グラハムの答えシェルピースを入手してください。ただし、スクリプトを実行するときにそのフラグを使用しない限り、すべてのデータがコピーされるように祈る必要がfullblockあることを忘れないでください。コピーが完了していない場合はゼロ以外の状態が返されるため、エラーを簡単に検出できますが、これを修正する実用的な方法はありません。dddd

POSIXはシェルレベルでより良いものを提供しません。私の提案は、小さな専用Cプログラムを書くことです(実装する内容に応じて、またはと呼ぶことdd_done_righttail_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)標準出力のブロック数を計算します。

最後にやるべきことは、モジュラスをコピーすることです。(そうであれば)以前の分割操作。それはすべてです。

ところが、人々が証拠もなく面前で事実を述べるなら信じないでください。はい、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です。

dd2番目の例では、書き込み全体が発生するまで出力ブロックサイズとバッファ読み取りを明示的に指定します。これは入力に基づくブロックには影響しませんが、count=これには他のブロックだけが必要ですdd。それ以外の場合は、提供されたエラーメッセージは無視する必要があります。

関連情報